/*
 * Copyright 2011-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.platform.framework.redis.util;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Range;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.Limit;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.RedisStreamCommands;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamInfo;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.connection.stream.StreamReadOptions;
import org.springframework.data.redis.connection.zset.Aggregate;
import org.springframework.data.redis.connection.zset.Weights;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.Assert;

import me.ijleex.platform.framework.json.util.JacksonUtils;

/**
 * Redis工具类.
 *
 * <p>使用{@link StringRedisTemplate}操作Redis。</p>
 *
 * @author liym
 * @author JustryDeng
 * @see #setRedisTemplate(StringRedisTemplate)
 * @see StringRedisTemplate
 * @see Keys
 * @see Strings
 * @see Lists
 * @see Sets
 * @see SortedSets
 * @see Hashes
 * @see Streams
 * @see Geo
 * @see Bitmaps
 * @see HyperLogLogs
 * @see Locks
 * @see org.springframework.data.redis.connection.RedisCommandsProvider
 * @see <a href="https://redis.io/docs/latest/commands/">Redis Commands</a>
 * @since 2023-08-06 16:59 新建
 */
public final class RedisUtils {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    private RedisUtils() {
        throw new IllegalAccessError("Utility class");
    }

    /**
     * 设置 {@link StringRedisTemplate}
     *
     * @param redisTemplate StringRedisTemplate
     */
    public static void setRedisTemplate(StringRedisTemplate redisTemplate) {
        Assert.notNull(redisTemplate, () -> "'StringRedisTemplate' must not be null");
        Lazy.redisTemplate = redisTemplate;
        logger.info("Set 'StringRedisTemplate' done");
    }

    /**
     * Redis键（Key）操作.
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=generic">Generic</a>
     */
    public static final class Keys {

        private Keys() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 删除指定的 key
         *
         * @param key 要删除的key
         * @return 删除是否成功，删除失败，则返回false。若redis中不存在指定的key，那么返回的也是false。
         * 所以，不能因为返回了false，就认为redis中一定还存在该key对应的key-value。
         * @see <a href="https://redis.io/docs/latest/commands/del/">del</a>
         * @since 2020-03-07 17:15:02
         */
        public static boolean del(String key) {
            logger.info("del(...) => key -> {}", key);
            // 返回值只可能为true/false，不可能为null
            Boolean result = Lazy.redisTemplate().delete(key);
            logger.info("del(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 批量删除指定的 key
         * <p>
         * 注：若redis中，不存在对应的key，那么计数不会加1。即：
         * redis中存在的key-value里，有名为a1、a2的key，
         * 删除时，传的集合是a1、a2、a3，那么返回结果为2。
         *
         * @param keys 要删除的key集合
         * @return 删除的key-value个数
         * @see <a href="https://redis.io/docs/latest/commands/del/">del</a>
         * @since 2020-03-07 17:48:04
         */
        public static long del(Collection<String> keys) {
            logger.info("del(...) => keys -> {}", keys);
            if (keys == null || keys.isEmpty()) {
                return 0L;
            }
            Long result = Lazy.redisTemplate().delete(keys);
            logger.info("del(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 将key对应的value值进行序列化，并返回序列化后的value值。
         * <p>
         * 注：若不存在对应的key，则返回null。
         * 注：dump时，并不会删除redis中的对应key-value。
         * 注：dump功能与restore相反。
         *
         * @param key 要序列化的value的key
         * @return 序列化后的value值
         * @see <a href="https://redis.io/docs/latest/commands/dump/">dump</a>
         * @see #restore
         * @since 2020-03-08 11:34:13
         */
        public static byte[] dump(String key) {
            logger.info("dump(...) => key -> {}", key);
            byte[] result = Lazy.redisTemplate().dump(key);
            logger.info("dump(...) => result -> {}", result);
            return result;
        }

        /**
         * 判断指定 key 是否存在
         *
         * @param key 指定的key
         * @return true/false
         * @see <a href="https://redis.io/docs/latest/commands/exists/">exists</a>
         * @since 2020-03-08 12:16:46
         */
        public static boolean exists(String key) {
            logger.info("exists(...) => key -> {}", key);
            Boolean result = Lazy.redisTemplate().hasKey(key);
            logger.info("exists(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 找到所有匹配pattern的key，并返回该key的结合
         * <p>
         * 提示：若redis中键值对较多，此方法耗时相对较长，慎用！慎用！慎用！
         *
         * @param pattern 匹配模板，支持常用通配符 ? *
         * @return 匹配pattern的key的集合，可能为null。
         * @see <a href="https://redis.io/docs/latest/commands/keys/">keys</a>
         * @since 2020-03-08 12:38:38
         */
        public static Set<String> keys(String pattern) {
            logger.info("keys(...) => pattern -> {}", pattern);
            Set<String> result = Lazy.redisTemplate().keys(pattern);
            logger.info("keys(...) => result -> {}", result);
            return result;
        }

        /**
         * 将当前数据库中的key对应的key-value，移动到对应位置的数据库中。
         * <p>
         * 注：单机版的redis,默认将存储分为16个db, index为0 到 15。
         * 注：同一个db下，key唯一；但是在不同db中，key可以相同。
         * 注：若目标db下，已存在相同的key，那么move会失败，返回false。
         *
         * @param key 定位要移动的key-value的key
         * @param dbIndex 要移动到哪个db
         * @return 移动是否成功。
         * 注：若目标db下，已存在相同的key，那么move会失败，返回false。
         * @see <a href="https://redis.io/docs/latest/commands/move/">move</a>
         * @since 2020-03-08 13:01:00
         */
        public static boolean move(String key, int dbIndex) {
            logger.info("move(...) => key -> {}, dbIndex -> {}", key, dbIndex);
            Boolean result = Lazy.redisTemplate().move(key, dbIndex);
            logger.info("move(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 移除key对应的key-value的过期时间, 使该key-value一直存在
         * <p>
         * 注：若key对应的key-value，本身就是一直存在(无过期时间的)，那么persist方法会返回false；
         * 若没有key对应的key-value存在，那么persist方法会返回false。
         *
         * @param key 定位key-value的key
         * @return 操作是否成功
         * @see <a href="https://redis.io/docs/latest/commands/persist/">persist</a>
         * @since 2020-03-08 13:10:02
         */
        public static boolean persist(String key) {
            logger.info("persist(...) => key -> {}", key);
            Boolean result = Lazy.redisTemplate().persist(key);
            logger.info("persist(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 给指定的key对应的key-value设置: 多久过时
         * <p>
         * 注:过时后，redis会自动删除对应的key-value。
         * 注:若key不存在，那么也会返回false。
         *
         * @param key 指定的key
         * @param timeout 过时时间
         * @param unit timeout的单位
         * @return 操作是否成功
         * @see <a href="https://redis.io/docs/latest/commands/pexpire/">pExpire</a>
         * @since 2020-03-08 12:18:58
         */
        public static boolean pExpire(String key, long timeout, TimeUnit unit) {
            logger.info("pExpire(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            Boolean result = Lazy.redisTemplate().expire(key, timeout, unit);
            logger.info("pExpire(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 给指定的key对应的key-value设置: 什么时候过时
         * <p>
         * 注：过时后，redis会自动删除对应的key-value。
         * 注：若key不存在，那么也会返回false。
         *
         * @param key 指定的key
         * @param date 过期时间
         * @return 操作是否成功
         * @see <a href="https://redis.io/docs/latest/commands/pexpireat/">pExpireAt</a>
         * @since 2020-03-08 12:19:29
         */
        public static boolean pExpireAt(String key, Date date) {
            logger.info("pExpireAt(...) => key -> {}, date -> {}", key, date);
            Boolean result = Lazy.redisTemplate().expireAt(key, date);
            logger.info("pExpireAt(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 获取key对应的key-value的过期时间
         * <p>
         * 注: 若key-value永不过期，那么返回的为-1。
         * 注: 若不存在key对应的key-value，那么返回的为-2
         * 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别。
         *
         * @param key 定位key-value的key
         * @return 过期时间（单位秒）
         * @see <a href="https://redis.io/docs/latest/commands/pttl/">pTtl</a>
         * @since 2020-03-08 13:17:35
         */
        public static long pTtl(String key) {
            return pTtl(key, TimeUnit.SECONDS);
        }

        /**
         * 获取key对应的key-value的过期时间
         * <p>
         * 注: 若key-value永不过期，那么返回的为-1。
         * 注: 若不存在key对应的key-value，那么返回的为-2
         * 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别。
         *
         * @param key 定位key-value的key
         * @return 过期时间(单位unit)
         * @see <a href="https://redis.io/docs/latest/commands/pttl/">pTtl</a>
         * @since 2020-03-08 13:17:35
         */
        public static long pTtl(String key, TimeUnit unit) {
            logger.info("pTtl(...) => key -> {}, unit -> {}", key, unit);
            Long result = Lazy.redisTemplate().getExpire(key, unit);
            logger.info("pTtl(...) => result -> {}", result);
            return result != null ? result : -2L;
        }

        /**
         * 从redis的所有key中，随机获取一个key
         * <p>
         * 注: 若redis中不存在任何key-value, 那么这里返回null
         *
         * @return 随机获取到的一个key
         * @see <a href="https://redis.io/docs/latest/commands/randomkey/">randomKey</a>
         * @since 2020-03-08 14:11:43
         */
        public static String randomKey() {
            String result = Lazy.redisTemplate().randomKey();
            logger.info("randomKey(...) => result -> {}", result);
            return result;
        }

        /**
         * 重命名对应的key为新的newKey
         * <p>
         * 注: 若key不存在，则会抛出异常.
         * 注: 若redis中已存在与newKey一样的key,
         * 那么原key-value会被丢弃，
         * 只留下新的key，以及原来的value
         * 示例说明: 假设redis中已有 (keyAlpha, valueAlpha) 和 (keyBeta, valueBeat),
         * 在使用rename(keyAlpha, keyBeta)替换后, redis中只会剩下(keyBeta, valueAlpha)
         *
         * @param key 旧的key
         * @param newKey 新的key
         * @throws RedisSystemException 若key不存在时，抛出此异常
         * @see <a href="https://redis.io/docs/latest/commands/rename/">rename</a>
         * @since 2020-03-08 14:14:17
         */
        public static void rename(String key, String newKey) {
            logger.info("rename(...) => key -> {}, newKey -> {}", key, newKey);
            Lazy.redisTemplate().rename(key, newKey);
        }

        /**
         * 当redis中不存在newKey时，重命名对应的key为新的newKey，否则不进行重命名操作。
         * <p>
         * 注：若key不存在，则会抛出异常。
         *
         * @param key 旧的key
         * @param newKey 新的key
         * @throws RedisSystemException 若key不存在时，抛出此异常
         * @see <a href="https://redis.io/docs/latest/commands/renamenx/">renameNX</a>
         * @since 2020-03-08 14:14:17
         */
        public static boolean renameNX(String key, String newKey) {
            logger.info("renameNX(...) => key -> {}, newKey -> {}", key, newKey);
            Boolean result = Lazy.redisTemplate().renameIfAbsent(key, newKey);
            logger.info("renameNX(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 将给定的value值，反序列化到redis中，形成新的key-value。
         *
         * @param key value对应的key
         * @param value 要反序列的value值。
         * 注: 这个值可以由{@link Keys#dump(String)}获得
         * @param timeToLive 反序列化后的key-value的存活时长
         * @param unit timeToLive的单位
         * @throws RedisSystemException 如果redis中已存在同样的key时，抛出此异常
         * @see <a href="https://redis.io/docs/latest/commands/restore/">restore</a>
         * @since 2020-03-08 11:36:45
         */
        public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {
            restore(key, value, timeToLive, unit, false);
        }

        /**
         * 将给定的value值，反序列化到redis中，形成新的key-value
         *
         * @param key value对应的key
         * @param value 要反序列的value值
         * 注: 这个值可以由{@link #dump(String)}获得
         * @param timeout 反序列化后的key-value的存活时长
         * @param unit timeout的单位
         * @param replace 若redis中已经存在了相同的key, 是否替代原来的key-value
         * @throws RedisSystemException 如果redis中已存在同样的key, 且replace为false时，抛出此异常
         * @see <a href="https://redis.io/docs/latest/commands/restore/">restore</a>
         * @see #dump(String)
         * @since 2020-03-08 11:36:45
         */
        public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {
            logger.info("restore(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, replace -> {}",
                    key, value, timeout, unit, replace);
            Lazy.redisTemplate().restore(key, value, timeout, unit, replace);
        }

        /**
         * 获取key对应的key-value的过期时间
         * <p>
         * 注: 若key-value永不过期，那么返回的为-1。
         * 注: 若不存在key对应的key-value，那么返回的为-2
         * 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别。
         *
         * @param key 定位key-value的key
         * @return 过期时间(单位unit)
         * @see <a href="https://redis.io/docs/latest/commands/ttl/">ttl</a>
         * @since 2023-11-17 21:49:58
         */
        public static long ttl(String key) {
            logger.info("ttl(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().getExpire(key);
            logger.info("ttl(...) => result -> {}", result);
            return result != null ? result : -2L;
        }

        /**
         * 获取key对应的value的数据类型
         * <p>
         * 注: 若redis中不存在该key对应的key-value，那么这里返回NONE。
         *
         * @param key 用于定位的key
         * @return key对应的value的数据类型
         * @see <a href="https://redis.io/docs/latest/commands/type/">type</a>
         * @since 2020-03-08 14:40:16
         */
        public static DataType type(String key) {
            logger.info("type(...) => key -> {}", key);
            DataType result = Lazy.redisTemplate().type(key);
            logger.info("type(...) => result -> {}", result);
            return result;
        }

    }

    /**
     * Redis字段串（String）操作.
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=string">String</a>
     */
    public static final class Strings {

        private Strings() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 追加值到末尾
         * <p>
         * 注：当redis中原本不存在key时，那么（从效果上来看）此方法就等价于{@link #set(String, String)}
         *
         * @param key 定位value的key
         * @param value 要追加的value值
         * @return 追加后，整个value的长度
         * @see <a href="https://redis.io/docs/latest/commands/append/">append</a>
         * @since 2020-03-08 17:59:21
         */
        public static int append(String key, String value) {
            logger.info("append(...) => key -> {}, value -> {}", key, value);
            Integer result = Lazy.redisTemplate().opsForValue().append(key, value);
            logger.info("append(...) => result -> {}", result);
            return result != null ? result : -1;
        }

        /**
         * 增/减 整数
         * <p>
         * 注：负数则为减。
         * 注：若key对应的value值不支持增/减操作(即：value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位value的key
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/decr/">decr</a>
         * @since 2023-08-13 13:25:39
         */
        public static long decr(String key) {
            logger.info("decr(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForValue().decrement(key);
            logger.info("decr(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 增/减 整数
         * <p>
         * 注: 负数则为减。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位value的key
         * @param increment 增加多少
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/decrby/">decrBy</a>
         * @since 2023-08-13 13:25:44
         */
        public static long decrBy(String key, long increment) {
            logger.info("decrBy(...) => key -> {}, increment -> {}", key, increment);
            Long result = Lazy.redisTemplate().opsForValue().decrement(key, increment);
            logger.info("decrBy(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 根据key，获取到对应的value值
         *
         * @param key key-value对应的key
         * @return 该key对应的值。
         * 注: 若key不存在，则返回null。
         * @see <a href="https://redis.io/docs/latest/commands/get/">get</a>
         * @since 2020-03-08 16:27:41
         */
        public static String get(String key) {
            logger.info("get(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForValue().get(key);
            logger.info("get(...) => result -> {}", result);
            return result;
        }

        /**
         * 根据key，获取到对应的value值
         *
         * @param key key-value对应的key
         * @return 该key对应的值。注: 若key不存在，则返回null。
         * @see <a href="https://redis.io/docs/latest/commands/getdel/">getDel</a>
         * @since 2023-08-13 13:25:13
         */
        public static String getDel(String key) {
            logger.info("getDel(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForValue().getAndDelete(key);
            logger.info("getDel(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取key对应的值，并设置key的过期时间
         *
         * @param key 定位value的key
         * @param timeout 过期时间
         * @param unit 过期时间单位
         * @return key对应的值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/getex/">getEX</a>
         * @since 2023-11-17 22:01
         */
        public static String getEX(String key, long timeout, TimeUnit unit) {
            logger.info("getEx(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            String result = Lazy.redisTemplate().opsForValue().getAndExpire(key, timeout, unit);
            logger.info("getEx(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取key对应的值，并设置key的过期时间
         *
         * @param key 定位value的key
         * @param timeout 过期时间
         * @return key对应的值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/getex/">getEX</a>
         * @since 2023-11-17 22:05:36
         */
        public static String getEX(String key, Duration timeout) {
            logger.info("getEX(...) => key -> {}, timeout -> {}", key, timeout);
            String result = Lazy.redisTemplate().opsForValue().getAndExpire(key, timeout);
            logger.info("getEX(...) => result -> {}", result);
            return result;
        }

        /**
         * 对(key对应的)value进行截取, 截取范围为[start, end]
         * <p>
         * 注: 若[start, end]的范围不在value的范围中，那么返回的是空字符串 ""
         * 注: 若value只有一部分在[start, end]的范围中，那么返回的是value对应部分的内容(即:不足的地方，并不会以空来填充)
         *
         * @param key 定位value的key
         * @param start 起始位置 (从0开始)
         * @param end 结尾位置 (从0开始)
         * @return 截取后的字符串
         * @see <a href="https://redis.io/docs/latest/commands/getrange/">getRange</a>
         * @since 2020-03-08 18:08:45
         */
        public static String getRange(String key, long start, long end) {
            logger.info("getRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            String result = Lazy.redisTemplate().opsForValue().get(key, start, end);
            logger.info("getRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 给指定key设置新的value, 并返回旧的value
         * <p>
         * 注: 若redis中不存在key, 那么此操作仍然可以成功，不过返回的旧值是null
         *
         * @param key 定位value的key
         * @param newValue 要为该key设置的新的value值
         * @return 旧的value值
         * @see <a href="https://redis.io/docs/latest/commands/getset/">getSet</a>
         * @since 2020-03-08 18:14:24
         */
        public static String getSet(String key, String newValue) {
            logger.info("getSet(...) => key -> {}, value -> {}", key, newValue);
            String result = Lazy.redisTemplate().opsForValue().getAndSet(key, newValue);
            logger.info("getSet(...) => result -> {}", result);
            return result;
        }

        /**
         * 增/减 整数
         * <p>
         * 注: 负数则为减。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位value的key
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/incr/">incr</a>
         * @since 2023-08-13 13:25:49
         */
        public static long incr(String key) {
            logger.info("incr(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForValue().increment(key);
            logger.info("incr(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 增/减 整数
         * <p>
         * 注: 负数则为减。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位value的key
         * @param increment 增加多少
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/incrby/">incrBy</a>
         * @since 2020-03-08 17:45:51
         */
        public static long incrBy(String key, long increment) {
            logger.info("incrBy(...) => key -> {}, increment -> {}", key, increment);
            Long result = Lazy.redisTemplate().opsForValue().increment(key, increment);
            logger.info("incrBy(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 增/减 浮点数
         * <p>
         * 注: 慎用浮点数，会有精度问题。
         * 如: 先 Strings.set("ds", "123");
         * 然后再Strings.incrByFloat("ds", 100.6);
         * 就会看到精度问题。
         * 注: 负数则为减。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位value的key
         * @param increment 增加多少
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/incrbyfloat/">incrByFloat</a>
         * @since 2020-03-08 17:45:51
         */
        public static double incrByFloat(String key, double increment) {
            logger.info("incrByFloat(...) => key -> {}, increment -> {}", key, increment);
            Double result = Lazy.redisTemplate().opsForValue().increment(key, increment);
            logger.info("incrByFloat(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 批量获取value值
         * <p>
         * 注: 若redis中，对应的key不存在，那么该key对应的返回的value值为null
         *
         * @param keys key集
         * @return value值集合
         * @see <a href="https://redis.io/docs/latest/commands/mget/">mGet</a>
         * @since 2020-03-08 18:26:33
         */
        public static List<String> mGet(Collection<String> keys) {
            logger.info("mGet(...) => keys -> {}", keys);
            List<String> result = Lazy.redisTemplate().opsForValue().multiGet(keys);
            logger.info("mGet(...) => result -> {}", result);
            return result;
        }

        /**
         * 批量设置 key-value
         * <p>
         * 注：若存在相同的key，则原来的key-value会被丢弃。
         *
         * @param values key-value 集
         * @see <a href="https://redis.io/docs/latest/commands/mset/">mSet</a>
         * @since 2020-03-08 17:21:19
         */
        public static void mSet(Map<String, String> values) {
            logger.info("mSet(...) => values -> {}", values);
            Lazy.redisTemplate().opsForValue().multiSet(values);
        }

        /**
         * 当redis中,不存在任何一个keys时, 才批量设置 key-value, 并返回成功/失败.
         * 否则，不进行任何操作, 并返回false。
         * <p>
         * 即: 假设调用此方法时传入的参数map是这样的: {k1=v1, k2=v2, k3=v3}
         * 那么redis中, k1、k2、k3都不存在时,才会批量设置key-value;
         * 否则不会设置任何key-value。
         * <p>
         * 注: 若存在相同的key, 则原来的key-value会被丢弃。
         * <p>
         *
         * @param values key-value 集
         * @return 操作是否成功
         * @see <a href="https://redis.io/docs/latest/commands/msetnx/">mSetNX</a>
         * @since 2020-03-08 17:21:19
         */
        public static boolean mSetNX(Map<String, String> values) {
            logger.info("mSetNX(...) => values -> {}", values);
            Boolean result = Lazy.redisTemplate().opsForValue().multiSetIfAbsent(values);
            logger.info("mSetNX(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 设置key-value
         * <p>
         * 注: 若已存在相同的key, 那么原来的key-value会被丢弃。
         *
         * @param key key
         * @param value key对应的value
         * @see <a href="https://redis.io/docs/latest/commands/set/">set</a>
         * @since 2020-03-08 15:40:59
         */
        public static void set(String key, String value) {
            logger.info("set(...) => key -> {}, value -> {}", key, value);
            Lazy.redisTemplate().opsForValue().set(key, value);
        }

        /**
         * 设置key-value
         * <p>
         * 注: 若已存在相同的key, 那么原来的key-value会被丢弃
         *
         * @param key key
         * @param value key对应的value
         * @param timeout 过时时长
         * @param unit timeout的单位
         * @see <a href="https://redis.io/docs/latest/commands/setex/">setEX</a>
         * @since 2020-03-08 15:40:59
         */
        public static void setEX(String key, String value, long timeout, TimeUnit unit) {
            logger.info("setEX(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",
                    key, value, timeout, unit);
            Lazy.redisTemplate().opsForValue().set(key, value, timeout, unit);
        }

        /**
         * 若不存在key时, 向redis中添加key-value, 返回成功/失败。
         * 若存在，则不作任何操作, 返回false。
         *
         * @param key key
         * @param value key对应的value
         * @return set是否成功
         * @see <a href="https://redis.io/docs/latest/commands/setnx/">setNX</a>
         * @since 2020-03-08 16:51:36
         */
        public static boolean setNX(String key, String value) {
            logger.info("setNX(...) => key -> {}, value -> {}", key, value);
            Boolean result = Lazy.redisTemplate().opsForValue().setIfAbsent(key, value);
            logger.info("setNX(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 若不存在key时，向redis中添加一个（具有超时时长的）key-value，返回成功/失败。
         * 若存在，则不作任何操作，返回false。
         *
         * @param key key
         * @param value key对应的value
         * @param timeout 超时时长
         * @param unit timeout的单位
         * @return set是否成功
         * @see <a href="https://redis.io/docs/latest/commands/setnx/">setNX</a>
         * @since 2020-03-08 16:51:36
         */
        public static boolean setNX(String key, String value, long timeout, TimeUnit unit) {
            logger.info("setNX(...) => key -> {}, value -> {}, key -> {}, value -> {}", key, value, timeout, unit);
            Boolean result = Lazy.redisTemplate().opsForValue().setIfAbsent(key, value, timeout, unit);
            logger.info("setNX(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 从(redis中key对应的)value的offset位置起(包含该位置),用replaceValue替换对应长度的值。
         * <p>
         * 举例说明:
         * 1.假设redis中存在key-value ("ds", "0123456789"); 调
         * 用setRange("ds", "abcdefghijk", 3)后，redis中该value值就变为了[012abcdefghijk]
         * <p>
         * 2.假设redis中存在key-value ("jd", "0123456789");调
         * 用setRange("jd", "xyz", 3)后，redis中该value值就变为了[012xyz6789]
         * <p>
         * 3.假设redis中存在key-value ("ey", "0123456789");调
         * 用setRange("ey", "qwer", 15)后，redis中该value值就变为了[0123456789     qwer]
         * 注:case3比较特殊，offset超过了原value的长度了，中间就会有一些空格来填充，但是如果在程序
         * 中直接输出的话，中间那部分空格可能会出现乱码。
         *
         * @param key 定位key-value的key
         * @param replaceValue 要替换的值
         * @param offset 起始位置
         * @see <a href="https://redis.io/docs/latest/commands/setrange/">setRange</a>
         * @since 2020-03-08 17:04:31
         */
        public static void setRange(String key, String replaceValue, long offset) {
            logger.info("setRange(...) => key -> {}, replaceValue -> {}, offset -> {}", key, replaceValue, offset);
            Lazy.redisTemplate().opsForValue().set(key, replaceValue, offset);
        }

        /**
         * 获取到key对应的value的长度。
         * <p>
         * 注：长度等于{@link String#length()}。
         *
         * @param key 定位value的key
         * @return value的长度，若redis中不存在对应的key-value，则返回值为0
         * @see <a href="https://redis.io/docs/latest/commands/strlen/">strLen</a>
         * @since 2020-03-08 17:14:30
         */
        public static long strLen(String key) {
            logger.info("strLen(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForValue().size(key);
            logger.info("strLen(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis链表（List）操作.
     * <p>
     * 提示：列表中的元素，可以重复。
     * <p>
     * 提示：list是有序的。
     * <p>
     * 提示：redis中的list中的索引，可分为两类,这两类都可以用来定位list中元素:
     * 类别一：从left到right, 是从0开始依次增大:   0,  1,  2,  3...
     * 类别二：从right到left, 是从-1开始依次减小: -1, -2, -3, -4...
     * <p>
     * 提示：redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例一).png
     * redis中String的数据结构可参考resources/data-structure/List(列表)的数据结构(示例二).png
     *
     * @author JustryDeng
     * @see <a href="https://redis.io/docs/latest/commands/?group=list">List</a>
     * @since 2020-03-09 11:30:48
     */
    public static final class Lists {

        private Lists() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 阻塞等待元素从sourceKey链表的尾部（右边）移出，然后添加到destinationKey的头部（左边）
         *
         * @param sourceKey 移出链表
         * @param destinationKey 添加链表
         * @param timeout 等待时间
         * @param unit 等待时间单位
         * @return 移动的元素
         * @author liym
         * @see #bRPopLPush(String, String, long, TimeUnit)
         * @see #bLMove(String, String, Duration)
         * @since 2023-11-30 21:10
         */
        public static String bLMove(String sourceKey, String destinationKey, long timeout, TimeUnit unit) {
            logger.info("bLMove(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {}, unit -> {}",
                    sourceKey, destinationKey, timeout, unit);
            String result = Lazy.redisTemplate().opsForList().move(
                    sourceKey, RedisListCommands.Direction.RIGHT,
                    destinationKey, RedisListCommands.Direction.LEFT,
                    timeout, unit);
            logger.info("bLMove(...) => result -> {}", result);
            return result;
        }

        /**
         * 阻塞等待元素从sourceKey链表的尾部（右边）移出，然后添加到destinationKey的头部（左边）
         *
         * @param sourceKey 移出链表
         * @param destinationKey 添加链表
         * @param timeout 等待时间
         * @return 移动的元素
         * @author liym
         * @see #bRPopLPush(String, String, Duration)
         * @see #bLMove(String, String, long, TimeUnit)
         * @since 2023-11-30 21:52
         */
        public static String bLMove(String sourceKey, String destinationKey, Duration timeout) {
            logger.info("bLMove(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {}",
                    sourceKey, destinationKey, timeout);
            String result = Lazy.redisTemplate().opsForList().move(
                    sourceKey, RedisListCommands.Direction.RIGHT,
                    destinationKey, RedisListCommands.Direction.LEFT,
                    timeout);
            logger.info("bLMove(...) => result -> {}", result);
            return result;
        }

        /// bLMPop  https://redis.io/docs/latest/commands/blmpop/

        /**
         * 【阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回
         * <p>
         * 注: 此方法是阻塞的，即: 若(key对应的)list中的所有元素都被pop移出了，此时，再进行pop的话，
         * 会阻塞timeout这么久，然后返回null
         * 注: 此方法是阻塞的，即: 若redis中不存在对应的key,那么会阻塞timeout这么久，然后返回null
         * 注: 若将(key对应的)list中的所有元素都pop完了，那么该key会被删除
         * <p>
         * 提示: 若阻塞过程中，目标key-list出现了，且里面有item了，那么会立马停止阻塞, 进行元素移出并返回
         *
         * @param key 定位list的key
         * @param timeout 超时时间
         * @param unit timeout的单位
         * @return 移出的那个元素
         * @see #bRPop(String, long, TimeUnit)
         * @see <a href="https://redis.io/docs/latest/commands/blpop/">bLPop</a>
         * @since 2020-03-09 14:33:56
         */
        public static String bLPop(String key, long timeout, TimeUnit unit) {
            logger.info("bLPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            String result = Lazy.redisTemplate().opsForList().leftPop(key, timeout, unit);
            logger.info("bLPop(...) => result -> {}", result);
            return result;
        }

        /**
         * 与{@link Lists#bLPop(String, long, TimeUnit)}类比即可，不过是从list右侧移出元素
         *
         * @see <a href="https://redis.io/docs/latest/commands/brpop/">bRPop</a>
         */
        public static String bRPop(String key, long timeout, TimeUnit unit) {
            logger.info("bRPop(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            String result = Lazy.redisTemplate().opsForList().rightPop(key, timeout, unit);
            logger.info("bRPop(...) => result -> {}", result);
            return result;
        }

        /**
         * 【阻塞队列】 从sourceKey对应的sourceList右侧移出一个item，并将这个item推
         * 入(destinationKey对应的)destinationList的左侧
         * <p>
         * 注: 若sourceKey对应的list中没有item了，则阻塞等待, 直到能从sourceList中移出一个非null的item(或等待时长超时);
         * case1: 等到了一个非null的item, 那么继续下面的push操作，并返回这个item。
         * case2: 超时了，还没等到非null的item, 那么pop出的结果就未null,此时并不会往destinationList进行push。
         * 此时，此方法的返回值是null。
         * <p>
         * 注: 若将(sourceKey对应的)list中的所有元素都pop完了，那么该sourceKey会被删除。
         *
         * @param sourceKey 定位sourceList的key
         * @param destinationKey 定位destinationList的key
         * @param timeout 超时时间
         * @param unit 超时时间单位
         * @return 移动的元素
         * @see #bRPopLPush(String, String, Duration)
         * @see #bLMove(String, String, long, TimeUnit)
         * @since 2020-03-09 15:06:59
         */
        public static String bRPopLPush(String sourceKey, String destinationKey, long timeout, TimeUnit unit) {
            logger.info("bRPopLPush(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {}, unit -> {}",
                    sourceKey, destinationKey, timeout, unit);
            String result = Lazy.redisTemplate().opsForList().rightPopAndLeftPush(
                    sourceKey, destinationKey, timeout, unit);
            logger.info("bRPopLPush(...) => result -> {}", result);
            return result;
        }

        /**
         * @see #bRPopLPush(String, String, long, TimeUnit)
         * @see #bLMove(String, String, Duration)
         * @since 2023-11-30 21:08
         */
        public static String bRPopLPush(String sourceKey, String destinationKey, Duration timeout) {
            logger.info("bRPopLPush(...) => sourceKey -> {}, destinationKey -> {}, timeout -> {}",
                    sourceKey, destinationKey, timeout);
            String result = Lazy.redisTemplate().opsForList().rightPopAndLeftPush(
                    sourceKey, destinationKey, timeout);
            logger.info("bRPopLPush(...) => result -> {}", result);
            return result;
        }

        /**
         * 通过索引index，获取（key对应的）list中的元素
         * <p>
         * 注：若key不存在 或 index超出（key对应的）list的索引范围，那么返回null
         *
         * @param key 定位list的key
         * @param index 定位list中的item的索引
         * @return list中索引index对应的item
         * @since 2020-03-10 00:27:23
         */
        public static String lIndex(String key, long index) {
            logger.info("lIndex(...) => key -> {}, index -> {}", key, index);
            String result = Lazy.redisTemplate().opsForList().index(key, index);
            logger.info("lIndex(...) => result -> {}", result);
            return result;
        }

        /**
         * 若key对应的list中存在pivot项，那么将item放入第一个pivot项前(即：放在第一个pivot项左边);
         * 若key对应的list中不存在pivot项，那么不做任何操作，直接返回-1。
         * <p>
         * 注: 若redis中不存在对应的key, 那么会自动创建
         *
         * @param key 定位list的key
         * @param element 要推入list的元素
         * @return 推入后，(key对应的)list的size
         * @since 2020-03-09 11:56:05
         */
        public static long lInsertBefore(String key, String pivot, String element) {
            logger.info("lInsert BEFORE(...) => key -> {}, pivot -> {}, element -> {}", key, pivot, element);
            Long result = Lazy.redisTemplate().opsForList().leftPush(key, pivot, element);
            logger.info("lInsert BEFORE(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 与{@link Lists#lInsertBefore(String, String, String)}类比即可，不过是从list右侧推入元素
         */
        public static long lInsertAfter(String key, String pivot, String element) {
            logger.info("lInsert AFTER(...) => key -> {}, pivot -> {}, element -> {}", key, pivot, element);
            Long result = Lazy.redisTemplate().opsForList().rightPush(key, pivot, element);
            logger.info("lInsert AFTER(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取(key对应的)list的size
         * <p>
         * 注: 当key不存在时，获取到的size为0。
         *
         * @param key 定位list的key
         * @return list的size。
         * @since 2020-03-10 00:48:40
         */
        public static long lLen(String key) {
            logger.info("lLen(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForList().size(key);
            logger.info("lLen(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 将元素从sourceKey链表的尾部（右边）移出，然后添加到destinationKey的头部（左边）
         *
         * @param sourceKey 移出链表
         * @param destinationKey 添加链表
         * @return 移动的元素
         * @author liym
         * @see #rPopLPush(String, String)
         * @see #bLMove(String, String, long, TimeUnit)
         * @since 2023-11-30 22:23
         */
        public static String lMove(String sourceKey, String destinationKey) {
            logger.info("lMove(...) => sourceKey -> {}, destinationKey -> {}", sourceKey, destinationKey);
            String result = Lazy.redisTemplate().opsForList().move(
                    sourceKey, RedisListCommands.Direction.RIGHT,
                    destinationKey, RedisListCommands.Direction.LEFT);
            logger.info("lMove(...) => result -> {}", result);
            return result;
        }

        /// lMPop  https://redis.io/docs/latest/commands/lmpop/

        /**
         * 【非阻塞队列】 从左侧移出(key对应的)list中的第一个元素, 并将该元素返回
         * <p>
         * 注: 此方法是非阻塞的，即: 若(key对应的)list中的所有元素都被pop移出了，此时，再进行pop的话，会立即返回null
         * 注: 此方法是非阻塞的，即: 若redis中不存在对应的key,那么会立即返回null
         * 注: 若将(key对应的)list中的所有元素都pop完了，那么该key会被删除
         *
         * @param key 定位list的key
         * @return 移出的那个元素
         * @since 2020-03-09 14:33:56
         */
        public static String lPop(String key) {
            logger.info("lPop(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForList().leftPop(key);
            logger.info("lPop(...) => result -> {}", result);
            return result;
        }

        /**
         * 查找列表中第一个出现的指定元素的索引位置（左 → 右）
         *
         * @param key 定位list的key
         * @param element 元素
         * @return 索引位置
         * @author liym
         * @since 2023-11-30 22:34
         */
        public static long lPos(String key, String element) {
            logger.info("lPos(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForList().indexOf(key, element);
            logger.info("lPos(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 查找列表中第一个出现的指定元素的索引位置（右→左）
         *
         * @param key 定位list的key
         * @param element 元素
         * @return 索引位置
         * @author liym
         * @since 2023-11-30 22:42
         */
        public static long lPosLast(String key, String element) {
            logger.info("lPos(...) => key -> {}, element -> {}, RANK -> -1", key, element);
            Long result = Lazy.redisTemplate().opsForList().lastIndexOf(key, element);
            logger.info("lPos(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从左端推入元素进列表
         * <p>
         * 注：若redis中不存在对应的key，那么会自动创建
         *
         * @param key 定位list的key
         * @param element 要推入list的元素
         * @return 推入后，(key对应的)list的size
         * @since 2020-03-09 11:56:05
         */
        public static long lPush(String key, String element) {
            logger.info("lPush(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForList().leftPush(key, element);
            logger.info("lPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从左端批量推入元素进列表
         *
         * @param key 定位list的key
         * @param elements 要批量推入list的元素集
         * @return 推入后，(key对应的)list的size
         * @since 2020-03-09 11:56:05
         */
        public static long lPush(String key, String... elements) {
            logger.info("lPush(...) => key -> {}, elements -> {}", key, elements);
            if (elements == null || elements.length == 0) {
                //throw new IllegalArgumentException("请指定要添加的元素：elements");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForList().leftPushAll(key, elements);
            logger.info("lPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从左端批量推入元素进列表
         *
         * @param key 定位list的key
         * @param elements 要批量推入list的元素集
         * @return 推入后，(key对应的)list的size
         * @since 2020-03-09 11:56:05
         */
        public static long lPush(String key, Collection<String> elements) {
            logger.info("lPush(...) => key -> {}, elements -> {}", key, elements);
            if (elements == null || elements.isEmpty()) {
                //throw new IllegalArgumentException("请指定要添加的元素：elements");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForList().leftPushAll(key, elements);
            logger.info("lPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 如果redis中存在key，则从左端批量推入元素进列表；否则，不进行任何操作
         *
         * @param key 定位list的key
         * @param element 要推入list的项
         * @return 推入后，(key对应的)list的size
         * @since 2020-03-09 13:40:08
         */
        public static long lPushX(String key, String element) {
            logger.info("lPushX(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForList().leftPushIfPresent(key, element);
            logger.info("lPushX(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取(key对应的)list中索引在[start, end]之间的item集
         * <p>
         * 注: 含start、含end。
         * 注: 当key不存在时，获取到的是空的集合。
         * 注: 当获取的范围比list的范围还要大时，获取到的是这两个范围的交集。
         * <p>
         * 提示: 可通过{@link Lists#lRange(String, long, long) Lists.lRange(key, 0, -1)}来获取到该key对应的整个list
         *
         * @param key 定位list的key
         * @param start 起始元素的index
         * @param end 结尾元素的index
         * @return 对应的元素集合
         * @since 2020-03-10 00:34:59
         */
        public static List<String> lRange(String key, long start, long end) {
            logger.info("lRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            List<String> result = Lazy.redisTemplate().opsForList().range(key, start, end);
            logger.info("lRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取(key对应的)list
         *
         * @param key 定位list的key
         * @return (key对应的)list
         * @see Lists#lRange(String, long, long)
         * @since 2020-03-10 00:46:50
         */
        public static List<String> lRange(String key) {
            return lRange(key, 0L, -1L);
        }

        /**
         * 删除(key对应的)list中，前expectCount个值等于item的项
         * <p>
         * 注: 若expectCount == 0，则表示删除list中所有的值等于item的项.
         * 注: 若expectCount > 0，则表示删除从左往右进行
         * 注: 若expectCount < 0，则表示删除从右往左进行
         * <p>
         * 注: 若list中,值等于item的项的个数少于expectCount时，那么会删除list中所有的值等于item的项。
         * 注: 当key不存在时, 返回0。
         * 注: 若lRemove后，将(key对应的)list中没有任何元素了，那么该key会被删除。
         *
         * @param key 定位list的key
         * @param count 要删除的item的个数
         * @param element 要删除的item
         * @return 实际删除了的item的个数
         * @since 2020-03-10 00:52:57
         */
        public static long lRem(String key, long count, String element) {
            logger.info("lRem(...) => key -> {}, count -> {}, element -> {}", key, count, element);
            Long result = Lazy.redisTemplate().opsForList().remove(key, count, element);
            logger.info("lRem(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 设置(key对应的)list中对应索引位置index处的元素为item
         * <p>
         * 注: 若key不存在，则会抛出org.springframework.data.redis.RedisSystemException
         * 注: 若索引越界，也会抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 定位list的key
         * @param index 定位list中的元素的索引
         * @param element 要替换成的值
         * @since 2020-03-09 15:39:50
         */
        public static void lSet(String key, long index, String element) {
            logger.info("lSet(...) => key -> {}, index -> {}, element -> {}", key, index, element);
            Lazy.redisTemplate().opsForList().set(key, index, element);
        }

        /**
         * 裁剪(即: 对list中的元素取交集。)
         * <p>
         * 举例说明: list中的元素索引范围是[0, 8], 而这个方法传入的[start, end]为 [3, 10]，
         * 那么裁剪就是对[0, 8]和[3, 10]进行取交集，得到[3, 8], 那么裁剪后
         * 的list中，只剩下(原来裁剪前)索引在[3, 8]之间的元素了。
         * <p>
         * 注: 若裁剪后的(key对应的)list就是空的,那么该key会被删除。
         *
         * @param key 定位list的key
         * @param start 要删除的item集的起始项的索引
         * @param end 要删除的item集的结尾项的索引
         * @since 2020-03-10 01:16:58
         */
        public static void lTrim(String key, long start, long end) {
            logger.info("lTrim(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Lazy.redisTemplate().opsForList().trim(key, start, end);
        }

        /**
         * 与{@link Lists#lPop(String)}类比即可，不过是从list右侧移出元素
         */
        public static String rPop(String key) {
            logger.info("rPop(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForList().rightPop(key);
            logger.info("rPop(...) => result -> {}", result);
            return result;
        }

        /**
         * 【非阻塞队列】 从sourceKey对应的sourceList右侧移出一个item，并将这个item推
         * 入(destinationKey对应的)destinationList的左侧
         * <p>
         * 注: 若sourceKey对应的list中没有item了，则立马认为(从sourceKey对应的list中pop出来的)item为null,
         * null并不会往destinationKey对应的list中push。
         * 追注：此时，此方法的返回值是null。
         * <p>
         * 注：若将(sourceKey对应的)list中的所有元素都pop完了，那么该sourceKey会被删除。
         *
         * @param sourceKey 定位sourceList的key
         * @param destinationKey 定位destinationList的key
         * @return 移动的这个元素
         * @since 2020-03-09 15:06:59
         */
        public static String rPopLPush(String sourceKey, String destinationKey) {
            logger.info("rPopLPush(...) => sourceKey -> {}, destinationKey -> {}", sourceKey, destinationKey);
            String result = Lazy.redisTemplate().opsForList().rightPopAndLeftPush(
                    sourceKey, destinationKey);
            logger.info("rPopLPush(...) => result -> {}", result);
            return result;
        }

        /**
         * 与{@link Lists#lPush(String, String)}类比即可，不过是从list右侧推入元素
         */
        public static long rPush(String key, String element) {
            logger.info("rPush(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForList().rightPush(key, element);
            logger.info("rPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 与{@link Lists#lPush(String, String...)}类比即可，不过是从list右侧推入元素
         */
        public static long rPush(String key, String... elements) {
            logger.info("rPush(...) => key -> {}, elements -> {}", key, elements);
            if (elements == null || elements.length == 0) {
                //throw new IllegalArgumentException("请指定要添加的元素：elements");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForList().rightPushAll(key, elements);
            logger.info("rPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 与{@link Lists#lPush(String, Collection)}类比即可，不过是从list右侧推入元素
         */
        public static long rPush(String key, Collection<String> elements) {
            logger.info("rPush(...) => key -> {}, elements -> {}", key, elements);
            if (elements == null || elements.isEmpty()) {
                //throw new IllegalArgumentException("请指定要添加的元素：elements");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForList().rightPushAll(key, elements);
            logger.info("rPush(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 与{@link Lists#lPushX(String, String)}类比即可，不过是从list右侧推入元素
         */
        public static long rPushX(String key, String element) {
            logger.info("rPushX(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForList().rightPushIfPresent(key, element);
            logger.info("rPushX(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis集合（Set）操作.
     *
     * <p>
     * 提示：set中的元素，不可以重复。
     * 提示：set是无序的。
     * 提示：redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例一).png
     * redis中String的数据结构可参考resources/data-structure/Set(集合)的数据结构(示例二).png
     *
     * @author JustryDeng
     * @see <a href="https://redis.io/docs/latest/commands/?group=set">Set</a>
     * @since 2020-03-09 11:30:48
     */
    public static final class Sets {

        private Sets() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 向(key对应的)set中添加items
         * <p>
         * 注: 若key不存在，则会自动创建。
         * 注: set中的元素会去重。
         *
         * @param key 定位set的key
         * @param members 要向(key对应的)set中添加的成员
         * @return 此次添加操作, 添加到set中的元素的个数
         * @see <a href="https://redis.io/docs/latest/commands/sadd/">sAdd</a>
         * @since 2020-03-11 8:16:00
         */
        public static long sAdd(String key, String... members) {
            logger.info("sAdd(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定要添加的元素：members");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForSet().add(key, members);
            logger.info("sAdd(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取(key对应的)set中的元素个数
         * <p>
         * 注：若key不存在，则返回0。
         *
         * @param key 定位set的key
         * @return (key对应的)set中的元素个数
         * @see <a href="https://redis.io/docs/latest/commands/scard/">sCard</a>
         * @since 2020-03-11 8:57:19
         */
        public static long sCard(String key) {
            logger.info("sCard(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForSet().size(key);
            logger.info("sCard(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集
         * <p>
         * 注: 如果被减数key不存在，那么结果为空的集合(而不是null)
         * 注: 如果被减数key存在，但减数key不存在，那么结果即为(被减数key对应的)Set
         *
         * @param key 定位"被减数set"的键
         * @param otherKey 定位"减数set"的键
         * @return item差集
         * @see <a href="https://redis.io/docs/latest/commands/sdiff/">sDiff</a>
         * @see #sDiff(String, Collection)
         * @see #sDiffStore(String, String, String)
         * @since 2020-03-11 14:03:57
         */
        public static Set<String> sDiff(String key, String otherKey) {
            logger.info("sDiff(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<String> result = Lazy.redisTemplate().opsForSet().difference(key, otherKey);
            logger.info("sDiff(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取 (key对应的)Set 减去 (otherKeys对应的)Sets 的差集
         * <p>
         * 注: 如果被减数key不存在，那么结果为空的集合(，而不是null)
         * 注: 如果被减数key存在，但减数key不存在，那么结果即为(被减数key对应的)Set
         * <p>
         * 提示: 当有多个减数时，被减数先减去哪一个减数，后减去哪一个减数，是无所谓的，是不影响最终结果的。
         *
         * @param key 定位"被减数set"的键
         * @param otherKeys 定位"减数集sets"的键集
         * @return item差集
         * @see <a href="https://redis.io/docs/latest/commands/sdiff/">sDiff</a>
         * @see #sDiff(String, String)
         * @since 2020-03-11 14:03:57
         */
        public static Set<String> sDiff(String key, Collection<String> otherKeys) {
            logger.info("sDiff(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<String> result = Lazy.redisTemplate().opsForSet().difference(key, otherKeys);
            logger.info("sDiff(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取 (key对应的)Set 减去 (otherKey对应的)Set 的差集, 并将结果add到storeKey对应的Set中。
         * <p>
         * case1：差集不为空，storeKey不存在，则 会创建对应的storeKey，并将差集添加到(storeKey对应的)set中
         * case2：差集不为空，storeKey已存在，则 会清除原(storeKey对应的)set中所有的项，然后将差集添加到(storeKey对应的)set中
         * case3：差集为空，则不进行下面的操作，直接返回0
         * <p>
         * 注：求并集的部分，详见{@link Sets#sDiff(String, String)}
         *
         * @param key 定位"被减数set"的键
         * @param otherKey 定位"减数set"的键
         * @param storeKey 定位(要把差集添加到哪个)set的key
         * @return add到(storeKey对应的)Set后, 该set对应的size
         * @see <a href="https://redis.io/docs/latest/commands/sdiffstore/">sDiffStore</a>
         * @see #sDiff(String, String)
         * @since 2020-03-11 14:33:36
         */
        public static long sDiffStore(String key, String otherKey, String storeKey) {
            logger.info("sDiffStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().differenceAndStore(
                    key, otherKey, storeKey);
            logger.info("sDiffStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取（key对应的）Set减去（otherKey对应的）Set的差集，并将结果add到storeKey对应的Set中
         * <p>
         * case1：差集不为空，storeKey不存在，则会创建对应的storeKey，并将差集添加到(storeKey对应的)set中
         * case2：差集不为空，storeKey已存在，则会清除原(storeKey对应的)set中所有的项，然后将差集添加到(storeKey对应的)set中
         * case3：差集为空，则不进行下面的操作，直接返回0
         * <p>
         * 注：求并集的部分，详见{@link Sets#sDiff(String, String)}
         *
         * @param key 定位"被减数set"的键
         * @param otherKeys 定位"减数集sets"的键集
         * @param storeKey 定位(要把差集添加到哪个)set的key
         * @return add到(storeKey对应的)Set后, 该set对应的size
         * @see <a href="https://redis.io/docs/latest/commands/sdiffstore/">sDiffStore</a>
         * @see #sDiff(String, Collection)
         * @since 2020-03-11 14:33:36
         */
        public static long sDiffStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("sDiffStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().differenceAndStore(
                    key, otherKeys, storeKey);
            logger.info("sDiffStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取两个（key对应的）Set的交集
         * <p>
         * 注：若不存在任何交集，那么返回空的集合（而不是null）
         * 注：若其中一个key不存在（或两个key都不存在），那么返回空的集合（而不是null）
         *
         * @param key 定位其中一个set的键
         * @param otherKey 定位其中另一个set的键
         * @return item交集
         * @see <a href="https://redis.io/docs/latest/commands/sinter/">sInter</a>
         * @since 2020-03-11 9:31:25
         */
        public static Set<String> sInter(String key, String otherKey) {
            logger.info("sInter(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<String> result = Lazy.redisTemplate().opsForSet().intersect(key, otherKey);
            logger.info("sInter(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取多个（key对应的）Set的交集
         * <p>
         * 注：若不存在任何交集，那么返回空的集合（而不是null）
         * 注：若>=1个key不存在，那么返回空的集合（而不是null）
         *
         * @param key 定位其中一个set的键
         * @param otherKeys 定位其它set的键集
         * @return item交集
         * @see <a href="https://redis.io/docs/latest/commands/sinter/">sInter</a>
         * @see #sInterStore(String, Collection, String)
         * @since 2020-03-11 9:39:23
         */
        public static Set<String> sInter(String key, Collection<String> otherKeys) {
            logger.info("sInter(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<String> result = Lazy.redisTemplate().opsForSet().intersect(key, otherKeys);
            logger.info("sInter(...) => result -> {}", result);
            return result;
        }

        /// sInterCard  https://redis.io/docs/latest/commands/sintercard/

        /**
         * 获取两个（key对应的）Set的交集，并将结果add到storeKey对应的Set中
         * <p>
         * case1：交集不为空，storeKey不存在，则会创建对应的storeKey，并将交集添加到(storeKey对应的)set中
         * case2：交集不为空，storeKey已存在，则会清除原(storeKey对应的)set中所有的项，然后将交集添加到(storeKey对应的)set中
         * case3：交集为空，则不进行下面的操作，直接返回0
         * <p>
         * 注：求交集的部分，详见{@link Sets#sInter(String, String)}
         *
         * @param key 定位其中一个set的键
         * @param otherKey 定位其中另一个set的键
         * @param storeKey 定位(要把交集添加到哪个)set的key
         * @return add到(storeKey对应的)Set后, 该set对应的size
         * @see <a href="https://redis.io/docs/latest/commands/sinterstore/">sInterStore</a>
         * @see #sInter(String, String)
         * @since 2020-03-11 9:46:46
         */
        public static long sInterStore(String key, String otherKey, String storeKey) {
            logger.info("sInterStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().intersectAndStore(
                    key, otherKey, storeKey);
            logger.info("sInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取多个(key对应的)Set的交集，并将结果add到storeKey对应的Set中。
         * <p>
         * case1: 交集不为空, storeKey不存在，则 会创建对应的storeKey，并将交集添加到(storeKey对应的)set中
         * case2: 交集不为空, storeKey已存在，则 会清除原(storeKey对应的)set中所有的项，然后将交集添加到(storeKey对应的)set中
         * case3: 交集为空, 则不进行下面的操作, 直接返回0
         * <p>
         * 注: 求交集的部分，详见{@link Sets#sInter(String, Collection)}
         *
         * @see <a href="https://redis.io/docs/latest/commands/sinterstore/">sInterStore</a>
         * @see #sInter(String, Collection)
         * @see #sInterStore(String, String, String)
         * @since 2020-03-11 11:04:29
         */
        public static long sInterStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("sInterStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().intersectAndStore(
                    key, otherKeys, storeKey);
            logger.info("sInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 判断（key对应的）set中是否含有item
         * <p>
         * 注：若key不存在，则返回false。
         *
         * @param key 定位set的key
         * @param member 被查找的项
         * @return （key对应的）set中是否含有item
         * @see <a href="https://redis.io/docs/latest/commands/sismember/">sIsMember</a>
         * @since 2020-03-11 9:03:29
         */
        public static boolean sIsMember(String key, Object member) {
            logger.info("sIsMember(...) => key -> {}, member -> {}", key, member);
            Boolean result = Lazy.redisTemplate().opsForSet().isMember(key, member);
            logger.info("sIsMember(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 获取key对应的set
         * <p>
         * 注：若key不存在，则返回的是空的set（而不是null）
         *
         * @param key 定位set的key
         * @return (key对应的)set
         * @see <a href="https://redis.io/docs/latest/commands/smembers/">sMembers</a>
         * @since 2020-03-11 14:49:39
         */
        public static Set<String> sMembers(String key) {
            logger.info("sMembers(...) => key -> {}", key);
            Set<String> result = Lazy.redisTemplate().opsForSet().members(key);
            logger.info("sMembers(...) => result -> {}", result);
            return result;
        }

        /**
         * 判断给定的member是为为特定集合的成员
         *
         * @param key 定位set的key
         * @param member 成员
         * @return true/false
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/smismember/">sMIsMember</a>
         * @since 2023-11-30 22:58
         */
        public static boolean sMIsMember(String key, Object member) {
            logger.info("sMIsMember(...) => key -> {}, member -> {}", key, member);
            Boolean result = Lazy.redisTemplate().opsForSet().isMember(key, member);
            logger.info("sMIsMember(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 判断给定的member是为为特定集合的成员
         *
         * @param key 定位set的key
         * @param members 成员
         * @return 成员 -> true/false
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/smismember/">sMIsMember</a>
         * @since 2023-11-30 23:03
         */
        public static Map<Object, Boolean> sMIsMember(String key, Object... members) {
            logger.info("sMIsMember(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定元素：members");
                return Collections.emptyMap();
            }
            Map<Object, Boolean> result = Lazy.redisTemplate().opsForSet().isMember(key, members);
            logger.info("sMIsMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 将(sourceKey对应的)sourceSet中的元素item, 移动到(destinationKey对应的)destinationSet中
         * <p>
         * 注: 当sourceKey不存在时，返回false
         * 注: 当item不存在时，返回false
         * 注: 若destinationKey不存在，那么在移动时会自动创建
         * 注: 若已经将(sourceKey对应的)set中的项move出去完了，那么对应的sourceKey会被删除。
         *
         * @param sourceKey 定位sourceSet的key
         * @param destinationKey 定位destinationSet的key
         * @param member 要移动的项目
         * @return 移动成功与否
         * @see <a href="https://redis.io/docs/latest/commands/smove/">sMove</a>
         * @since 2020-03-11 8:43:32
         */
        public static boolean sMove(String sourceKey, String destinationKey, String member) {
            logger.info("sMove(...) => sourceKey -> {}, destinationKey -> {}, member -> {}",
                    sourceKey, destinationKey, member);
            Boolean result = Lazy.redisTemplate().opsForSet().move(
                    sourceKey, member, destinationKey);
            logger.info("sMove(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 从（key对应的）set中随机移出一个item，并返回这个item
         * <p>
         * 注：因为set是无序的，所以移出的这个item是随机的；并且，哪怕是数据一样的set，多次测试移出操作，移除的元素也是随机的。
         * <p>
         * 注：若已经将(key对应的)set中的项pop完了，那么对应的key会被删除。
         *
         * @param key 定位set的key
         * @return 移出的项
         * @see <a href="https://redis.io/docs/latest/commands/spop/">sPop</a>
         * @since 2020-03-11 8:32:40
         */
        public static String sPop(String key) {
            logger.info("sPop(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForSet().pop(key);
            logger.info("sPop(...) => result -> {}", result);
            return result;
        }

        /**
         * 从key对应的set中随机获取一项
         *
         * @param key 定位set的key
         * @return 随机获取到的项
         * @see <a href="https://redis.io/docs/latest/commands/srandmember/">sRandMember</a>
         * @since 2020-03-11 14:54:58
         */
        public static String sRandMember(String key) {
            logger.info("sRandMember(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForSet().randomMember(key);
            logger.info("sRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 从key对应的set中获取count次随机项(, set中的同一个项可能被多次获取)
         * <p>
         * 注: count可大于set的size。
         * 注: 取出来的结果里可能存在相同的值。
         *
         * @param key 定位set的key
         * @param count 要取多少项
         * @return 随机获取到的项集
         * @see <a href="https://redis.io/docs/latest/commands/srandmember/">sRandMember</a>
         * @since 2020-03-11 14:54:58
         */
        public static List<String> sRandMember(String key, long count) {
            logger.info("sRandMember(...) => key -> {}, count -> {}", key, count);
            List<String> result = Lazy.redisTemplate().opsForSet().randomMembers(key, count);
            logger.info("sRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 从key对应的set中随机获取count个项
         * <p>
         * 注：若count >= set的size，那么返回的即为这个key对应的set。
         * 注：取出来的结果里没有重复的项。
         *
         * @param key 定位set的key
         * @param count 要取多少项
         * @return 随机获取到的项集
         * @see <a href="https://redis.io/docs/latest/commands/srandmember/">sRandMember</a>
         * @since 2020-03-11 14:54:58
         */
        public static Set<String> sRandMemberDistinct(String key, long count) {
            logger.info("sRandMember(...) => key -> {}, count -> {}", key, count);
            Set<String> result = Lazy.redisTemplate().opsForSet().distinctRandomMembers(key, count);
            logger.info("sRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 从（key对应的）set中删除items
         * <p>
         * 注：若key不存在，则返回0。
         * 注：若已经将（key对应的）set中的项删除完了，那么对应的key也会被删除。
         *
         * @param key 定位set的key
         * @param members 要移除的成员
         * @return 实际删除了的个数
         * @see <a href="https://redis.io/docs/latest/commands/srem/">sRem</a>
         * @since 2020-03-11 8:26:43
         */
        public static long sRem(String key, Object... members) {
            logger.info("sRem(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定要移除的元素：members");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForSet().remove(key, members);
            logger.info("sRem(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 根据options匹配到(key对应的)set中的对应的item, 并返回对应的member集
         * <p>
         * 注: ScanOptions实例的创建方式举例:
         * 1、ScanOptions.NONE
         * 2、ScanOptions.scanOptions().match("n??e").build()
         *
         * @param key 定位set的key
         * @param options 匹配set中的item的条件
         * 注: ScanOptions.NONE表示全部匹配。
         * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
         * 其中pattern中可以使用通配符 * ? 等,
         * * 表示>=0个字符
         * ？ 表示有且只有一个字符
         * 此处的匹配规则与{@link Keys#keys(String)}处的一样。
         * @return 匹配到的(key对应的)set中的项
         * @see <a href="https://redis.io/docs/latest/commands/sscan/">sScan</a>
         * @since 2020-03-09 10:49:27
         */
        public static Cursor<String> sScan(String key, ScanOptions options) {
            logger.info("sScan(...) => key -> {}, options -> {}", key, options.toOptionString());
            Cursor<String> cursor = Lazy.redisTemplate().opsForSet().scan(key, options);
            logger.info("sScan(...) => cursor -> {}", JacksonUtils.objToJson(cursor));
            return cursor;
        }

        /**
         * 获取两个(key对应的)Set的并集
         * <p>
         * 注：并集中的元素也是唯一的，这是Set保证的。
         *
         * @param key 定位其中一个set的键
         * @param otherKey 定位其中另一个set的键
         * @return item并集
         * @see <a href="https://redis.io/docs/latest/commands/sunion/">sUnion</a>
         * @since 2020-03-11 11:18:35
         */
        public static Set<String> sUnion(String key, String otherKey) {
            logger.info("sUnion(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<String> result = Lazy.redisTemplate().opsForSet().union(key, otherKey);
            logger.info("sUnion(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取两个（key对应的）Set的并集
         * <p>
         * 注：并集中的元素也是唯一的，这是Set保证的。
         *
         * @param key 定位其中一个set的键
         * @param otherKeys 定位其它set的键集
         * @return item并集
         * @see <a href="https://redis.io/docs/latest/commands/sunion/">sUnion</a>
         * @since 2020-03-11 11:18:35
         */
        public static Set<String> sUnion(String key, Collection<String> otherKeys) {
            logger.info("sUnion(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<String> result = Lazy.redisTemplate().opsForSet().union(key, otherKeys);
            logger.info("sUnion(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取两个(key对应的)Set的并集, 并将结果add到storeKey对应的Set中。
         * <p>
         * case1: 并集不为空, storeKey不存在，则 会创建对应的storeKey，并将并集添加到(storeKey对应的)set中
         * case2: 并集不为空, storeKey已存在，则 会清除原(storeKey对应的)set中所有的项，然后将并集添加到(storeKey对应的)set中
         * case3: 并集为空, 则不进行下面的操作, 直接返回0
         * <p>
         * 注: 求并集的部分，详见{@link Sets#sUnion(String, String)}
         *
         * @param key 定位其中一个set的键
         * @param otherKey 定位其中另一个set的键
         * @param storeKey 定位(要把并集添加到哪个)set的key
         * @return add到(storeKey对应的)Set后, 该set对应的size
         * @see <a href="https://redis.io/docs/latest/commands/sunionstore/">sUnionStore</a>
         * @since 2020-03-11 12:26:24
         */
        public static long sUnionStore(String key, String otherKey, String storeKey) {
            logger.info("sUnionStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().unionAndStore(
                    key, otherKey, storeKey);
            logger.info("sUnionStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取两个(key对应的)Set的并集，并将结果add到storeKey对应的Set中。
         * <p>
         * case1：并集不为空，storeKey不存在，则 会创建对应的storeKey，并将并集添加到(storeKey对应的)set中；
         * <br>
         * case2：并集不为空，storeKey已存在，则 会清除原(storeKey对应的)set中所有的项，然后将并集添加到(storeKey对应的)set中；
         * <br>
         * case3：并集为空，则不进行下面的操作，直接返回0。
         * <p>
         * 注：求并集的部分，详见{@link Sets#sUnion(String, Collection)}
         *
         * @param key 定位其中一个set的键
         * @param otherKeys 定位其它set的键集
         * @param storeKey 定位(要把并集添加到哪个)set的key
         * @return add到(storeKey对应的)Set后, 该set对应的size
         * @see <a href="https://redis.io/docs/latest/commands/sunionstore/">sUnionStore</a>
         * @since 2020-03-11 12:26:24
         */
        public static long sUnionStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("sUnionStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForSet().unionAndStore(
                    key, otherKeys, storeKey);
            logger.info("sUnionStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis有序集合（ZSet）操作.
     *
     * <p>
     * 特别说明：ZSet是有序的,
     * 不仅体现在： redis中的存储上有序。
     * 还体现在：此工具类SortedSets中返回值类型为Set<?>的方法, 实际返回类型是LinkedHashSet<?>
     * <p>
     * 提示: redis中的ZSet, 一定程度等于redis中的Set + redis中的Hash的结合体。
     * 提示: redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例一).png
     * redis中String的数据结构可参考resources/data-structure/ZSet(有序集合)的数据结构(示例二).png
     * 提示: ZSet中的field即为成员项，member即为这个成员项的分值, ZSet根据成员的分值，来堆成员进行排序。
     *
     * @author JustryDeng
     * @see <a href="https://redis.io/docs/latest/commands/?group=sorted-set">Sorted Set</a>
     * @since 2020-03-11 15:28:48
     */
    public static final class SortedSets {

        private SortedSets() {
            throw new IllegalAccessError("No instance");
        }

        /// bZMPop  https://redis.io/docs/latest/commands/bzmpop/

        /**
         * 阻塞等待移除并返回score最高的元素
         *
         * @param key 定位set的key
         * @param timeout 等待时间
         * @param unit 等待时间单位
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bzpopmax/">bZPopMax</a>
         * @see #zPopMax(String)
         * @see #zPopMax(String, long)
         * @since 2023-12-02 12:16
         */
        public static ZSetOperations.TypedTuple<String> bZPopMax(String key, long timeout, TimeUnit unit) {
            logger.info("bZPopMax(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet()
                    .popMax(key, timeout, unit);
            logger.info("bZPopMax(...) => result -> {}", result);
            return result;
        }

        /**
         * 阻塞等待移除并返回score最高的元素
         *
         * @param key 定位set的key
         * @param timeout 等待时间
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bzpopmax/">bZPopMax</a>
         * @see #zPopMax(String)
         * @see #zPopMax(String, long)
         * @since 2023-12-02 12:17
         */
        public static ZSetOperations.TypedTuple<String> bZPopMax(String key, Duration timeout) {
            logger.info("bZPopMax(...) => key -> {}, timeout -> {}", key, timeout);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet()
                    .popMax(key, timeout);
            logger.info("bZPopMax(...) => result -> {}", result);
            return result;
        }

        /**
         * 阻塞等待移除并返回score最低的元素
         *
         * @param key 定位set的key
         * @param timeout 等待时间
         * @param unit 等待时间单位
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bzpopmin/">bZPopMin</a>
         * @see #zPopMin(String)
         * @see #zPopMin(String, long)
         * @since 2023-12-02 12:22
         */
        public static ZSetOperations.TypedTuple<String> bZPopMin(String key, long timeout, TimeUnit unit) {
            logger.info("bZPopMin(...) => key -> {}, timeout -> {}, unit -> {}", key, timeout, unit);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet()
                    .popMin(key, timeout, unit);
            logger.info("bZPopMin(...) => result -> {}", result);
            return result;
        }

        /**
         * 阻塞等待移除并返回score最低的元素
         *
         * @param key 定位set的key
         * @param timeout 等待时间
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bzpopmin/">bZPopMin</a>
         * @see #zPopMin(String)
         * @see #zPopMin(String, long)
         * @since 2023-12-02 12:22
         */
        public static ZSetOperations.TypedTuple<String> bZPopMin(String key, Duration timeout) {
            logger.info("bZPopMin(...) => key -> {}, timeout -> {}", key, timeout);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet()
                    .popMin(key, timeout);
            logger.info("bZPopMin(...) => result -> {}", result);
            return result;
        }

        /**
         * 向(key对应的)zset中添加(member, score)
         * <p>
         * 注: item为field成员项，score为member分数值。
         * <p>
         * 注: 若(key对应的)zset中已存在(与此次要添加的项)相同的item项，那么此次添加操作会失败，返回false；
         * 但是！！！ zset中原item的score会被更新为此次add的相同item项的score。
         * 所以, 也可以通过zAdd达到更新item对应score的目的。
         * <p>
         * 注: score可为正、可为负、可为0; 总之, double范围内都可以。
         * <p>
         * 注: 若score的值一样，则按照item排序。
         *
         * @param key 定位set的key
         * @param member 要往(key对应的)zset中添加的成员项
         * @param score item的分值
         * @return 是否添加成功
         * @see <a href="https://redis.io/docs/latest/commands/zadd/">zAdd</a>
         * @since 2020-03-11 15:35:30
         */
        public static boolean zAdd(String key, String member, double score) {
            logger.info("zAdd(...) => key -> {}, member -> {}, score -> {}", key, member, score);
            Boolean result = Lazy.redisTemplate().opsForZSet().add(key, member, score);
            logger.info("zAdd(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 批量添加entry<member, score>
         * <p>
         * 注: 若entry<member, score>集中存在item相同的项(score不一样)，那么redis在执行真正的批量add操作前,会
         * 将其中一个item过滤掉。
         * 注: 同样的，若(key对应的)zset中已存在(与此次要添加的项)相同的item项，那么此次批量添加操作中，
         * 对该item项的添加会失败，会失败，成功计数器不会加1；但是！！！ zset中原item的score会被更新为此
         * 次add的相同item项的score。所以, 也可以通过zAdd达到更新item对应score的目的。
         *
         * @param key 定位set的key
         * @param members 要添加的entry<member, score>集
         * @return 本次添加进(key对应的)zset中的entry的个数
         * @see <a href="https://redis.io/docs/latest/commands/zadd/">zAdd</a>
         * @since 2020-03-11 16:45:45
         */
        public static long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> members) {
            logger.info("zAdd(...) => key -> {}, members -> {}", key, members);
            Long result = Lazy.redisTemplate().opsForZSet().add(key, members);
            logger.info("zAdd(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 统计(key对应的)zset中元素的个数
         *
         * @param key 定位zset的key
         * @return zset中元素的个数
         * @see <a href="https://redis.io/docs/latest/commands/zcard/">zCard</a>
         * @since 2020-03-13 12:20:43
         */
        public static long zCard(String key) {
            logger.info("zCard(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForZSet().zCard(key);
            logger.info("zCard(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 统计(key对应的zset中)score处于[minScore, maxScore]中的member的个数
         *
         * @param key 定位zset的key
         * @param minScore score下限
         * @param maxScore score上限
         * @return [minScore, maxScore]中item的个数
         * @see <a href="https://redis.io/docs/latest/commands/zcount/">zCount</a>
         * @since 2020-03-13 12:20:43
         */
        public static long zCount(String key, double minScore, double maxScore) {
            logger.info("zCount(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);
            Long result = Lazy.redisTemplate().opsForZSet().count(key, minScore, maxScore);
            logger.info("zCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 求两个集合的差集
         *
         * @param key 第一个集合
         * @param otherKey 第二个集合
         * @return 差集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zdiff/">zDiff</a>
         * @since 2023-11-30 23:16
         */
        public static Set<String> zDiff(String key, String otherKey) {
            logger.info("zDiff(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<String> result = Lazy.redisTemplate().opsForZSet().difference(key, otherKey);
            logger.info("zDiff(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的差集
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @return 差集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zdiff/">zDiff</a>
         * @since 2023-11-30 23:17
         */
        public static Set<String> zDiff(String key, Collection<String> otherKeys) {
            logger.info("zDiff(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<String> result = Lazy.redisTemplate().opsForZSet().difference(key, otherKeys);
            logger.info("zDiff(...) => result -> {}", result);
            return result;
        }

        /**
         * 求两个集合的差集
         *
         * @param key 第一个集合
         * @param otherKey 第二个集合
         * @return 差集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zdiff/">zDiff</a>
         * @since 2023-11-30 23:22
         */
        public static Set<ZSetOperations.TypedTuple<String>> zDiffWithScores(String key, String otherKey) {
            logger.info("zDiffWithScores(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .differenceWithScores(key, otherKey);
            logger.info("zDiffWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的差集
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @return 差集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zdiff/">zDiff</a>
         * @since 2023-11-30 23:23
         */
        public static Set<ZSetOperations.TypedTuple<String>> zDiffWithScores(String key, Collection<String> otherKeys) {
            logger.info("zDiffWithScores(...) => key -> {}, otherKey -> {}", key, otherKeys);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .differenceWithScores(key, otherKeys);
            logger.info("zDiffWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 求集合与其他集合的差集，并将结果保存到目标集合中
         *
         * @param key 第一个集合
         * @param otherKeys 其他集合
         * @param storeKey 差集结果保存集合
         * @return 差集元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zdiffstore/">zDiffStore</a>
         * @since 2023-11-30 23:28
         */
        public static long zDiffStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("zDiffStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForZSet().differenceAndStore(key, otherKeys, storeKey);
            logger.info("sUnionStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 增/减 (key对应的zset中)item的分数值
         *
         * @param key 定位zset的key
         * @param member 项
         * @param increment 变化量(正 - 增，负 - 减)
         * @return 修改后的score值
         * @see <a href="https://redis.io/docs/latest/commands/zincrby/">zIncrBy</a>
         * @since 2020-03-12 8:55:38
         */
        public static double zIncrBy(String key, String member, double increment) {
            logger.info("zIncrBy(...) => key -> {}, member -> {}, increment -> {}", key, member, increment);
            Double result = Lazy.redisTemplate().opsForZSet().incrementScore(key, member, increment);
            logger.info("zIncrBy(...) => result -> {}", result);
            return result != null ? result : -1.0D;
        }

        /**
         * 求两个集合的交集
         *
         * @param key 第一个集合
         * @param otherKey 第二个集合
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-11-30 23:37
         */
        public static Set<String> zInter(String key, String otherKey) {
            logger.info("zInter(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<String> result = Lazy.redisTemplate().opsForZSet().intersect(key, otherKey);
            logger.info("zInter(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的交集
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-11-30 23:38:43
         */
        public static Set<String> zInter(String key, Collection<String> otherKeys) {
            logger.info("zInter(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<String> result = Lazy.redisTemplate().opsForZSet().intersect(key, otherKeys);
            logger.info("zInter(...) => result -> {}", result);
            return result;
        }

        /**
         * 求两个集合的交集，返回结果元素包含权重
         *
         * @param key 第一个集合
         * @param otherKey 第二个集合
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-12-02 10:35
         */
        public static Set<ZSetOperations.TypedTuple<String>> zInterWithScores(String key, String otherKey) {
            logger.info("zInterWithScores(...) => key -> {}, otherKey -> {}", key, otherKey);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .intersectWithScores(key, otherKey);
            logger.info("zInterWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的交集，返回结果元素包含权重
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-12-02 10:37
         */
        public static Set<ZSetOperations.TypedTuple<String>> zInterWithScores(String key, Collection<String> otherKeys) {
            logger.info("zInterWithScores(...) => key -> {}, otherKeys -> {}", key, otherKeys);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .intersectWithScores(key, otherKeys);
            logger.info("zInterWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的交集，返回结果元素包含权重
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @param aggregate 成员分数值的计算方式
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-12-02 10:46
         */
        public static Set<ZSetOperations.TypedTuple<String>> zInterWithScores(
                String key, Collection<String> otherKeys, Aggregate aggregate) {
            logger.info("zInterWithScores(...) => key -> {}, otherKeys -> {}, aggregate -> {}",
                    key, otherKeys, aggregate);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .intersectWithScores(key, otherKeys, aggregate);
            logger.info("zInterWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 求多个集合的交集，返回结果元素包含权重
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @param aggregate 成员分数值的计算方式
         * @param weights 集合成员的加权因子，每个有序集合中所有成员的 score 值在传递给聚合函数(aggregation function)之前，都要先乘以该加权因子
         * @return 交集
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinter/">zInter</a>
         * @since 2023-12-02 10:57
         */
        public static Set<ZSetOperations.TypedTuple<String>> zInterWithScores(
                String key, Collection<String> otherKeys, Aggregate aggregate, Weights weights) {
            logger.info("zInterWithScores(...) => key -> {}, otherKeys -> {}, aggregate -> {}, weights -> {}",
                    key, otherKeys, aggregate, weights);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .intersectWithScores(key, otherKeys, aggregate, weights);
            logger.info("zInterWithScores(...) => result -> {}", result);
            return result;
        }

        /// zInterCard  https://redis.io/docs/latest/commands/zintercard/

        /**
         * 获取两个(key对应的)ZSet的交集，并将结果add到storeKey对应的ZSet中。
         * <p>
         * 注: 和set一样，zset中item是唯一的，在多个zset进行Intersect时, 处理相同的item时，score的值会变为对应的score之和，如：
         * RedisUtil.SortedSets.zAdd("name1", "a", 1);
         * RedisUtil.SortedSets.zAdd("name1", "b", 100);
         * 和
         * RedisUtil.SortedSets.zAdd("name2", "a", 2);
         * RedisUtil.SortedSets.zAdd("name2", "c", 200);
         * 对(name1和name2对应的)zset进行zInterStore之后，新的zset中的项a,对应的score值为3
         * <p>
         * case1：交集不为空，storeKey不存在，则 会创建对应的storeKey，并将交集添加到(storeKey对应的)ZSet中
         * case2：交集不为空，storeKey已存在，则 会清除原(storeKey对应的)ZSet中所有的项，然后将交集添加到(storeKey对应的)ZSet中
         * case3：交集为空，则不进行下面的操作，直接返回0
         *
         * @param key 定位其中一个ZSet的键
         * @param otherKey 定位其中另一个ZSet的键
         * @param storeKey 定位(要把交集添加到哪个)ZSet的key
         * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
         * @see <a href="https://redis.io/docs/latest/commands/zinterstore/">zInterStore</a>
         * @since 2020-03-11 09:46:46
         */
        public static long zInterStore(String key, String otherKey, String storeKey) {
            logger.info("zInterStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
            Long result = Lazy.redisTemplate().opsForZSet().intersectAndStore(key, otherKey, storeKey);
            logger.info("zInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取多个(key对应的)ZSet的交集, 并将结果add到storeKey对应的ZSet中。
         * <p>
         * case1：交集不为空，storeKey不存在，则 会创建对应的storeKey，并将交集添加到(storeKey对应的)ZSet中
         * case2：交集不为空，storeKey已存在，则 会清除原(storeKey对应的)ZSet中所有的项，然后将交集添加到(storeKey对应的)ZSet中
         * case3：交集为空，则不进行下面的操作，直接返回0
         *
         * @param key 定位其中一个set的键
         * @param otherKeys 定位其它set的键集
         * @param storeKey 定位(要把并集添加到哪个)set的key
         * @return add到(storeKey对应的)ZSet后，该ZSet对应的size
         * @see <a href="https://redis.io/docs/latest/commands/zinterstore/">zInterStore</a>
         * @since 2020-03-11 11:04:29
         */
        public static long zInterStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("zInterStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}",
                    key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForZSet().intersectAndStore(key, otherKeys, storeKey);
            logger.info("zInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取多个(key对应的)ZSet的交集，并将结果add到storeKey对应的ZSet中
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @param storeKey 结果保存集合
         * @param aggregate 成员分数值的计算方式
         * @return 添加到结果集合的元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinterstore/">zInterStore</a>
         * @since 2023-12-02 11:25
         */
        public static long zInterStore(String key, Collection<String> otherKeys, String storeKey,
                Aggregate aggregate) {
            logger.info("zInterStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}, aggregate -> {}",
                    key, otherKeys, storeKey, aggregate);
            Long result = Lazy.redisTemplate().opsForZSet().intersectAndStore(key, otherKeys, storeKey, aggregate);
            logger.info("zInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取多个(key对应的)ZSet的交集，并将结果add到storeKey对应的ZSet中
         *
         * @param key 第一个集合
         * @param otherKeys 其他的集合
         * @param storeKey 结果保存集合
         * @param aggregate 成员分数值的计算方式
         * @param weights 集合成员的加权因子
         * @return 添加到结果集合的元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zinterstore/">zInterStore</a>
         * @since 2023-12-02 11:30
         */
        public static long zInterStore(String key, Collection<String> otherKeys, String storeKey,
                Aggregate aggregate, Weights weights) {
            logger.info("zInterStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}, aggregate -> {}, weights -> {}",
                    key, otherKeys, storeKey, aggregate, weights);
            Long result = Lazy.redisTemplate().opsForZSet()
                    .intersectAndStore(key, otherKeys, storeKey, aggregate, weights);
            logger.info("zInterStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 计算有序集合中指定字典区间内成员数量
         *
         * @param key 定位set的key
         * @param range 字典区间
         * @return 成员数量
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zlexcount/">zLexCount</a>
         * @since 2023-12-02 11:39
         */
        public static long zLexCount(String key, Range<String> range) {
            logger.info("zLexCount(...) => key -> {}, range -> {}", key, range);
            Long result = Lazy.redisTemplate().opsForZSet().lexCount(key, range);
            logger.info("zLexCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /// zMPop  https://redis.io/docs/latest/commands/zmpop/

        /**
         * 获取元素的分值
         *
         * @param key 定位zset的键
         * @param member 集合成员
         * @return 集合成员的分值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zmscore/">zMScore</a>
         * @since 2023-12-02 11:49
         */
        public static Double zMScore(String key, Object member) {
            logger.info("zMScore(...) => key -> {}, member -> {}", key, member);
            Double result = Lazy.redisTemplate().opsForZSet().score(key, member);
            logger.info("zMScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取多个元素的分值
         *
         * @param key 定位zset的键
         * @param members 集合成员
         * @return 集合成员的分值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zmscore/">zMScore</a>
         * @since 2023-12-02 11:48
         */
        public static List<Double> zMScore(String key, Object... members) {
            logger.info("zMScore(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定元素：members");
                return Collections.emptyList();
            }
            List<Double> result = Lazy.redisTemplate().opsForZSet().score(key, members);
            logger.info("zMScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 移除并返回score最高的元素
         *
         * @param key 定位set的key
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zpopmax/">zPopMax</a>
         * @see #bZPopMax(String, Duration)
         * @see #bZPopMax(String, long, TimeUnit)
         * @since 2023-12-02 12:11
         */
        public static ZSetOperations.TypedTuple<String> zPopMax(String key) {
            logger.info("zPopMax(...) => key -> {}", key);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet().popMax(key);
            logger.info("zPopMax(...) => result -> {}", result);
            return result;
        }

        /**
         * 移除并返回前count个score较高的元素
         *
         * @param key 定位zset的key
         * @param count 获取元素个数
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zpopmax/">zPopMax</a>
         * @see #bZPopMax(String, Duration)
         * @see #bZPopMax(String, long, TimeUnit)
         * @since 2023-12-02 12:14
         */
        public static Set<ZSetOperations.TypedTuple<String>> zPopMax(String key, long count) {
            logger.info("zPopMax(...) => key -> {}, count -> {}", key, count);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .popMax(key, count);
            logger.info("zPopMax(...) => result -> {}", result);
            return result;
        }

        /**
         * 移除并返回score最低的元素
         *
         * @param key 定位set的key
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zpopmin/">zPopMin</a>
         * @see #bZPopMin(String, Duration)
         * @see #bZPopMin(String, long, TimeUnit)
         * @since 2023-12-02 12:21:32
         */
        public static ZSetOperations.TypedTuple<String> zPopMin(String key) {
            logger.info("zPopMin(...) => key -> {}", key);
            ZSetOperations.TypedTuple<String> result = Lazy.redisTemplate().opsForZSet().popMin(key);
            logger.info("zPopMin(...) => result -> {}", result);
            return result;
        }

        /**
         * 移除并返回前count个score较低的元素
         *
         * @param key 定位zset的key
         * @param count 获取元素个数
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zpopmin/">zPopMin</a>
         * @see #bZPopMin(String, Duration)
         * @see #bZPopMin(String, long, TimeUnit)
         * @since 2023-12-02 12:21:32
         */
        public static Set<ZSetOperations.TypedTuple<String>> zPopMin(String key, long count) {
            logger.info("zPopMin(...) => key -> {}, count -> {}", key, count);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet().popMin(key, count);
            logger.info("zPopMin(...) => result -> {}", result);
            return result;
        }

        /**
         * 从集合中随机获取一个元素
         *
         * @param key 定位set的key
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrandmember/">zRandMember</a>
         * @since 2023-12-02 12:29
         */
        public static String zRandMember(String key) {
            logger.info("zRandMember(...) => key -> {}", key);
            String result = Lazy.redisTemplate().opsForZSet().randomMember(key);
            logger.info("zRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 从集合中随机获取指定个数的元素
         *
         * @param key 定位set的key
         * @param count 获取元素个数
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrandmember/">zRandMember</a>
         * @since 2023-12-02 12:31
         */
        public static List<String> zRandMember(String key, long count) {
            logger.info("zRandMember(...) => key -> {}, count -> {}", key, count);
            List<String> result = Lazy.redisTemplate().opsForZSet().randomMembers(key, count);
            logger.info("zRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 从集合中随机获取指定个数的元素
         *
         * @param key 定位set的key
         * @param count 获取元素个数
         * @return 元素（去重）
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrandmember/">zRandMember</a>
         * @since 2023-12-02 12:34
         */
        public static Set<String> zRandMemberDistinct(String key, long count) {
            logger.info("zRandMember(...) => key -> {}, count -> {}", key, count);
            Set<String> result = Lazy.redisTemplate().opsForZSet().distinctRandomMembers(key, count);
            logger.info("zRandMember(...) => result -> {}", result);
            return result;
        }

        /**
         * 根据索引位置，获取(key对应的)zset中排名处于[start, end]中的member项集
         * <p>
         * 注: 不论是使用正向排名，还是使用反向排名，使用此方法时，应保证startIndex代表的元素的
         * 位置在endIndex代表的元素的位置的前面，如：
         * 示例一: SortedSets.zRange("name", 0, 2);
         * 示例二: SortedSets.zRange("site", -2, -1);
         * 示例三: SortedSets.zRange("foo", 0, -1);
         * <p>
         * 注: 若key不存在, 则返回空的集合。
         * <p>
         * 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。
         *
         * @param key 定位zset的key
         * @param start 排名开始位置
         * @param end 排名结束位置
         * @return 对应的item项集
         * @see <a href="https://redis.io/docs/latest/commands/zrange/">zRange</a>
         * @since 2020-03-12 9:50:40
         */
        public static Set<String> zRange(String key, long start, long end) {
            logger.info("zRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Set<String> result = Lazy.redisTemplate().opsForZSet().range(key, start, end);
            logger.info("zRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取(key对应的)zset中的所有member项
         *
         * @param key 定位zset的键
         * @return (key对应的)zset中的所有member项
         * @see <a href="https://redis.io/docs/latest/commands/zrange/">zRange</a>
         * @see SortedSets#zRange(String, long, long)
         * @since 2020-03-12 10:02:07
         */
        public static Set<String> zRange(String key) {
            return zRange(key, 0L, -1L);
        }

        /**
         * 根据索引位置，获取(key对应的)zset中排名处于[start, end]中的entry集
         * <p>
         * 注: 不论是使用正向排名，还是使用反向排名，使用此方法时，应保证startIndex代表的元素的
         * 位置在endIndex代表的元素的位置的前面，如：
         * 示例一: SortedSets.zRange("name", 0, 2);
         * 示例二: SortedSets.zRange("site", -2, -1);
         * 示例三: SortedSets.zRange("foo", 0, -1);
         * <p>
         * 注: 若key不存在, 则返回空的集合。
         * <p>
         * 注: 当[start, end]的范围比实际zset的范围大时, 返回范围上"交集"对应的项集合。
         * <p>
         * 注: 此方法和{@link SortedSets#zRange(String, long, long)}类似，不过此方法返回的不是member集，而是entry集
         *
         * @param key 定位zset的key
         * @param start 排名开始位置
         * @param end 排名结束位置
         * @return 对应的entry集
         * @see <a href="https://redis.io/docs/latest/commands/zrange/">zRange</a>
         * @since 2020-03-12 9:50:40
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {
            logger.info("zRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .rangeWithScores(key, start, end);
            logger.info("zRangeWithScores(...) => result -> {}", JacksonUtils.objToJson(result));
            return result;
        }

        /**
         * 获取(key对应的)zset中的所有entry
         *
         * @param key 定位zset的键
         * @return (key对应的)zset中的所有entry
         * @see <a href="https://redis.io/docs/latest/commands/zrange/">zRange</a>
         * @see SortedSets#zRangeWithScores(String, long, long)
         * @since 2020-03-12 10:02:07
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key) {
            return zRangeWithScores(key, 0L, -1L);
        }

        /**
         * 按字典从低到高排序，取索引范围内的元素（集合内的分值需相同）
         *
         * @param key 定位set的key
         * @param range 字典区间
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrangebylex/">zRangeByLex</a>
         * @since 2023-12-02 13:14
         */
        public static Set<String> zRangeByLex(String key, Range<String> range) {
            logger.info("zRangeByLex(...) => key -> {}, range -> {}", key, range);
            Set<String> result = Lazy.redisTemplate().opsForZSet().rangeByLex(key, range);
            logger.info("zRangeByLex(...) => result -> {}", result);
            return result;
        }

        /**
         * 按字典从低到高排序，分页取索引范围内的元素（集合内的分值需相同）
         *
         * @param key 定位set的key
         * @param range 字典区间
         * @param limit 分页参数
         * @return 元素
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrangebylex/">zRangeByLex</a>
         * @since 2023-12-02 13:18
         */
        public static Set<String> zRangeByLex(String key, Range<String> range, Limit limit) {
            logger.info("zRangeByLex(...) => key -> {}, range -> {}, limit -> {}", key, range, limit);
            Set<String> result = Lazy.redisTemplate().opsForZSet().rangeByLex(key, range, limit);
            logger.info("zRangeByLex(...) => result -> {}", result);
            return result;
        }

        /**
         * 根据score，获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集
         * <p>
         * 注: 若key不存在, 则返回空的集合。
         * 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。
         * <p>
         * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题，所以建议:
         * 设置值时，minScore应该设置得比要删除的项里，最小的score还小一点
         * maxScore应该设置得比要删除的项里，最大的score还大一点
         * 追注: 本人简单测试了几组数据，暂未出现精度问题。
         *
         * @param key 定位zset的key
         * @param minScore score下限
         * @param maxScore score上限
         * @return 对应的item项集
         * @see <a href="https://redis.io/docs/latest/commands/zrangebyscore/">zRangeByScore</a>
         * @since 2020-03-12 9:50:40
         */
        public static Set<String> zRangeByScore(String key, double minScore, double maxScore) {
            logger.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}", key, minScore, maxScore);
            Set<String> result = Lazy.redisTemplate().opsForZSet().rangeByScore(
                    key, minScore, maxScore);
            logger.info("zRangeByScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 根据score，获取(key对应的)zset中分数值处于[minScore, maxScore]中的, score处于[minScore,
         * 排名大于等于offset的count个member项
         * <p>
         * 特别注意：对于不是特别熟悉redis的人来说，offset和count最好都使用正数，避免引起理解上的歧义。
         * <p>
         * 注: 若key不存在, 则返回空的集合。
         * <p>
         * 提示: 虽然删除范围包含两侧的端点(即：包含minScore和maxScore), 但是由于double存在精度问题，所以建议:
         * 设置值时，minScore应该设置得比要删除的项里，最小的score还小一点
         * maxScore应该设置得比要删除的项里，最大的score还大一点
         * 追注: 本人简单测试了几组数据，暂未出现精度问题。
         *
         * @param key 定位zset的key
         * @param minScore score下限
         * @param maxScore score上限
         * @param offset 偏移量(即：排名下限)
         * @param count 期望获取到的元素个数
         * @return 对应的item项集
         * @see <a href="https://redis.io/docs/latest/commands/zrangebyscore/">zRangeByScore</a>
         * @since 2020-03-12 9:50:40
         */
        public static Set<String> zRangeByScore(String key, double minScore, double maxScore,
                long offset, long count) {
            logger.info("zRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, count -> {}",
                    key, minScore, maxScore, offset, count);
            Set<String> result = Lazy.redisTemplate().opsForZSet().rangeByScore(
                    key, minScore, maxScore, offset, count);
            logger.info("zRangeByScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry
         * <p>
         * 注: 若key不存在, 则返回空的集合。
         * 注: 当[minScore, maxScore]的范围比实际zset中score的范围大时, 返回范围上"交集"对应的项集合。
         *
         * @param key 定位zset的键
         * @param minScore score下限
         * @param maxScore score上限
         * @return (key对应的)zset中的所有score处于[minScore, maxScore]中的entry
         * @see <a href="https://redis.io/docs/latest/commands/zrangebyscore/">zRangeByScore</a>
         * @see SortedSets#zRangeByScore(String, double, double)
         * @since 2020-03-12 10:02:07
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double minScore, double maxScore) {
            logger.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",
                    key, minScore, maxScore);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .rangeByScoreWithScores(key, minScore, maxScore);
            logger.info("zRangeByScoreWithScores(...) => result -> {}", JacksonUtils.objToJson(result));
            return result;
        }

        /**
         * 获取(key对应的)zset中, score处于[minScore, maxScore]里的、排名大于等于offset的count个entry
         * <p>
         * 特别注意: 对于不是特别熟悉redis的人来说, offset 和 count最好都使用正数，避免引起理解上的歧义。
         *
         * @param key 定位zset的键
         * @param minScore score下限
         * @param maxScore score上限
         * @param offset 偏移量(即：排名下限)
         * @param count 期望获取到的元素个数
         * @return [startIndex, endIndex] & [minScore, maxScore]里的entry
         * @see <a href="https://redis.io/docs/latest/commands/zrangebyscore/">zRangeByScore</a>
         * @since 2020-03-12 11:09:06
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(
                String key, double minScore, double maxScore, long offset, long count) {
            logger.info("zRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, count -> {}",
                    key, minScore, maxScore, offset, count);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .rangeByScoreWithScores(key, minScore, maxScore, offset, count);
            logger.info("zRangeByScoreWithScores(...) => result -> {}", result);
            return result;
        }

        /**
         * 从一个有序集合中筛选出一些元素放入目标集合
         *
         * @param key 定位其中一个set的键
         * @param storeKey 结果保存集合
         * @param range 筛选区间
         * @return 筛选元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrangestore/">zRangeStore</a>
         * @since 2023-12-02 16:15
         */
        public static long zRangeStoreByLex(String key, String storeKey, Range<String> range) {
            logger.info("zRangeStoreByLex(...) => key -> {}, storeKey -> {}, range -> {}", key, storeKey, range);
            Long result = Lazy.redisTemplate().opsForZSet().rangeAndStoreByLex(
                    key, storeKey, range);
            logger.info("zRangeStoreByLex(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从一个有序集合中筛选出一些元素放入目标集合
         *
         * @param key 定位其中一个set的键
         * @param storeKey 结果保存集合
         * @param range 筛选区间
         * @param limit 分页参数
         * @return 筛选元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zrangestore/">zRangeStore</a>
         * @since 2023-12-02 16:21
         */
        public static long zRangeStoreByLex(String key, Range<String> range, String storeKey, Limit limit) {
            logger.info("zRangeStoreByLex(...) => key -> {}, storeKey -> {}, range -> {}, limit -> {}", key, storeKey, range, limit);
            Long result = Lazy.redisTemplate().opsForZSet().rangeAndStoreByLex(
                    key, storeKey, range, limit);
            logger.info("zRangeStoreByLex(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 返回member在(key对应的)zset中的(按score从小到大的)排名
         * <p>
         * 注: 排名从0开始。 即意味着，此方法等价于: 返回member在(key对应的)zset中的位置索引。
         * 注: 若key或item不存在，返回null。
         * 注: 排序规则是score,member, 即:优先以score排序，若score相同，则再按member排序。
         *
         * @param key 定位zset的key
         * @param member 项
         * @return 排名(等价于 : 索引)
         * @see <a href="https://redis.io/docs/latest/commands/zrank/">zRank</a>
         * @since 2020-03-12 9:14:09
         */
        public static long zRank(String key, Object member) {
            logger.info("zRank(...) => key -> {}, member -> {}", key, member);
            Long result = Lazy.redisTemplate().opsForZSet().rank(key, member);
            logger.info("zRank(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从(key对应的)zset中移除项
         * <p>
         * 注:若key不存在，则返回0
         *
         * @param key 定位set的key
         * @param members 要移除的项集
         * @return 实际移除了的项的个数
         * @see <a href="https://redis.io/docs/latest/commands/zrem/">zRem</a>
         * @since 2020-03-11 17:20:12
         */
        public static long zRem(String key, Object... members) {
            logger.info("zRem(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定要移除的元素：members");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForZSet().remove(key, members);
            logger.info("zRem(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从有序集合中删除字典顺序在指定区间之间的成员
         *
         * @param key 定位zset的key
         * @param range 区间
         * @return 删除元素个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zremrangebylex/">zRemRangeByLex</a>
         * @since 2023-12-02 17:29
         */
        public static long zRemRangeByLex(String key, Range<String> range) {
            logger.info("zRemRangeByLex(...) => key -> {}, range -> {}", key, range);
            Long result = Lazy.redisTemplate().opsForZSet().removeRangeByLex(key, range);
            logger.info("zRemRangeByLex(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 移除(key对应的)zset中，排名范围在[startIndex, endIndex]内的成员
         * <p>
         * 注：默认的，按score.item升序排名，排名从0开始
         * <p>
         * 注: 类似于List中的索引，排名可以分为多个方式：
         * 从前到后(正向)的排名: 0、1、2...
         * 从后到前(反向)的排名: -1、-2、-3...
         * <p>
         * 注: 不论是使用正向排名，还是使用反向排名, 使用此方法时, 应保证 startRange代表的元素的位置
         * 在endRange代表的元素的位置的前面，如:
         * 示例一: SortedSets.zRemRangeByRank("name", 0, 2);
         * 示例二: SortedSets.zRemRangeByRank("site", -2, -1);
         * 示例三: SortedSets.zRemRangeByRank("foo", 0, -1);
         * <p>
         * 注:若key不存在，则返回0
         *
         * @param key 定位set的key
         * @param startRange 开始项的排名
         * @param endRange 结尾项的排名
         * @return 实际移除了的项的个数
         * @see <a href="https://redis.io/docs/latest/commands/zremrangebyrank/">zRemRangeByRank</a>
         * @since 2020-03-11 17:20:12
         */
        public static long zRemRangeByRank(String key, long startRange, long endRange) {
            logger.info("zRemRangeByRank(...) => key -> {}, startRange -> {}, endRange -> {}", key, startRange, endRange);
            Long result = Lazy.redisTemplate().opsForZSet().removeRange(key, startRange, endRange);
            logger.info("zRemRangeByRank(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 移除(key对应的)zset中, score范围在[minScore, maxScore]内的member
         * <p>
         * 提示: 虽然删除范围包含两侧的端点(即:包含minScore和maxScore), 但是由于double存在精度问题，所以建议:
         * 设置值时，minScore应该设置得比要删除的项里，最小的score还小一点
         * maxScore应该设置得比要删除的项里，最大的score还大一点
         * 追注: 本人简单测试了几组数据，暂未出现精度问题。
         * <p>
         * 注:若key不存在，则返回0
         *
         * @param key 定位set的key
         * @param minScore score下限(含这个值)
         * @param maxScore score上限(含这个值)
         * @return 实际移除了的项的个数
         * @see <a href="https://redis.io/docs/latest/commands/zremrangebyscore/">zRemRangeByScore</a>
         * @since 2020-03-11 17:20:12
         */
        public static long zRemRangeByScore(String key, double minScore, double maxScore) {
            logger.info("zRemRangeByScore(...) => key -> {}, startIndex -> {}, startIndex -> {}", key, minScore, maxScore);
            Long result = Lazy.redisTemplate().opsForZSet().removeRangeByScore(
                    key, minScore, maxScore);
            logger.info("zRemRangeByScore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取时，先按score倒序，然后根据索引位置，获取(key对应的)zset中排名处于[start, end]中的item项集
         *
         * @see <a href="https://redis.io/docs/latest/commands/zrevrange/">zRevRange</a>
         * @see SortedSets#zRange(String, long, long)。 只是zReverseRange这里会提前多一个倒序。
         */
        public static Set<String> zRevRange(String key, long start, long end) {
            logger.info("zRevRange(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Set<String> result = Lazy.redisTemplate().opsForZSet().reverseRange(
                    key, start, end);
            logger.info("zRevRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取时，先按score倒序，然后根据索引位置，获取(key对应的)zset中排名处于[start, end]中的entry集
         *
         * @see <a href="https://redis.io/docs/latest/commands/zrevrange/">zRevRange</a>
         * @see SortedSets#zRangeWithScores(String, long, long)。 只是zReverseRangeWithScores这里会提前多一个倒序。
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRevRangeWithScores(String key, long start, long end) {
            logger.info("zRevRangeWithScores(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .reverseRangeWithScores(key, start, end);
            logger.info("zRevRangeWithScores(...) => result -> {}", JacksonUtils.objToJson(result));
            return result;
        }

        /**
         * 获取时，先按score倒序，然后根据score，获取(key对应的)zset中分数值处于[minScore, maxScore]中的item项集
         *
         * @see <a href="https://redis.io/docs/latest/commands/zrevrangebyscore/">zRevRangeByScore</a>
         * @see SortedSets#zRangeByScore(String, double, double)。 只是zReverseRangeByScore这里会提前多一个倒序。
         */
        public static Set<String> zRevRangeByScore(String key, double minScore, double maxScore) {
            logger.info("zRevRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}",
                    key, minScore, maxScore);
            Set<String> result = Lazy.redisTemplate().opsForZSet()
                    .reverseRangeByScore(key, minScore, maxScore);
            logger.info("zRevRangeByScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取时, 先按score倒序, 然后获取(key对应的)zset中的所有score处于[minScore, maxScore]中的entry
         *
         * @see <a href="https://redis.io/docs/latest/commands/zrevrangebyscore/">zRevRangeByScore</a>
         * @see SortedSets#zRangeByScoreWithScores(String, double, double)。 只是zReverseRangeByScoreWithScores这里会提前多一个倒序。
         */
        public static Set<ZSetOperations.TypedTuple<String>> zRevRangeByScoreWithScores(String key, double minScore, double maxScore) {
            logger.info("zRevRangeByScoreWithScores(...) => key -> {}, minScore -> {}, maxScore -> {}",
                    key, minScore, maxScore);
            Set<ZSetOperations.TypedTuple<String>> result = Lazy.redisTemplate().opsForZSet()
                    .reverseRangeByScoreWithScores(key, minScore, maxScore);
            logger.info("zRevRangeByScoreWithScores(...) => result -> {}", JacksonUtils.objToJson(result));
            return result;
        }

        /**
         * 获取时, 先按score倒序, 然后根据score，获取(key对应的)zset中分数值处于[minScore, maxScore]中的,
         * score处于[minScore,排名大于等于offset的count个item项
         *
         * @see <a href="https://redis.io/docs/latest/commands/zrevrangebyscore/">zRevRangeByScore</a>
         * @see SortedSets#zRangeByScore(String, double, double, long, long)。 只是zReverseRangeByScore这里会提前多一个倒序。
         */
        public static Set<String> zRevRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
            logger.info("zRevRangeByScore(...) => key -> {}, minScore -> {}, maxScore -> {}, offset -> {}, count -> {}",
                    key, minScore, maxScore, offset, count);
            Set<String> result = Lazy.redisTemplate().opsForZSet()
                    .reverseRangeByScore(key, minScore, maxScore, offset, count);
            logger.info("zRevRangeByScore(...) => result -> {}", result);
            return result;
        }

        /**
         * 返回member在(key对应的)zset中的(按score从大到小的)排名
         * <p>
         * 注: 排名从0开始。补充: 因为是按score从大到小排序的, 所以最大score对应的member的排名为0。
         * 注: 若key或member不存在，返回null。
         * 注: 排序规则是score,member, 即:优先以score排序，若score相同，则再按member排序。
         *
         * @param key 定位zset的key
         * @param member 项
         * @return 排名(等价于 : 索引)
         * @see <a href="https://redis.io/docs/latest/commands/zrevrank/">zRevRank</a>
         * @since 2020-03-12 9:14:09
         */
        public static long zRevRank(String key, Object member) {
            logger.info("zRevRank(...) => key -> {}, member -> {}", key, member);
            Long result = Lazy.redisTemplate().opsForZSet().reverseRank(key, member);
            logger.info("zRevRank(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 迭代有序集合中的元素（包括元素成员和元素分值）
         *
         * @param key 定位zset的key
         * @param options 迭代条件
         * @return 游标
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/zscan/">zScan</a>
         * @see Cursor#close()
         * @since 2023-12-02 18:16
         */
        public static Cursor<ZSetOperations.TypedTuple<String>> zScan(String key, ScanOptions options) {
            logger.info("zScan(...) => key -> {}, options -> {}", key, options.toOptionString());
            Cursor<ZSetOperations.TypedTuple<String>> cursor = Lazy.redisTemplate().opsForZSet().scan(key, options);
            logger.info("zScan(...) => cursor -> {}", JacksonUtils.objToJson(cursor));
            return cursor;
        }

        /**
         * 获取有序集中，成员的分数值
         *
         * @param key 定位zset的key
         * @param member zset中的member
         * @return item的score
         * @see <a href="https://redis.io/docs/latest/commands/zscore/">zScore</a>
         * @since 2020-03-13 14:51:43
         */
        public static double zScore(String key, Object member) {
            logger.info("zScore(...) => key -> {}, member -> {}", key, member);
            Double result = Lazy.redisTemplate().opsForZSet().score(key, member);
            logger.info("zScore(...) => result -> {}", result);
            return result != null ? result : -1.0D;
        }

        /**
         * 获取两个(key对应的)ZSet的并集，并将结果add到storeKey对应的ZSet中
         * <p>
         * 注：和set一样，zset中item是唯一的，在多个zset进行Union时，处理相同的item时，score的值会变为对应的score之和，如：
         * SortedSets.zAdd("name1", "a", 1);和SortedSets.zAdd("name2", "a", 2);
         * 对(name1和name2对应的)zset进行zUnionAndStore之后，新的zset中的项a，对应的score值为3
         * <p>
         * case1：交集不为空，storeKey不存在，则会创建对应的storeKey，并将并集添加到(storeKey对应的)ZSet中
         * case2：交集不为空，storeKey已存在，则会清除原(storeKey对应的)ZSet中所有的项，然后将并集添加到(storeKey对应的)ZSet中
         * case3：交集为空，则不进行下面的操作，直接返回0
         *
         * @param key 定位其中一个zset的键
         * @param otherKey 定位另外的zset的键
         * @param storeKey 定位(要把交集添加到哪个)set的key
         * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
         * @see <a href="https://redis.io/docs/latest/commands/zunionstore/">zUnionStore</a>
         * @since 2020-03-11 12:26:24
         */
        public static long zUnionStore(String key, String otherKey, String storeKey) {
            logger.info("zUnionStore(...) => key -> {}, otherKey -> {}, storeKey -> {}", key, otherKey, storeKey);
            Long result = Lazy.redisTemplate().opsForZSet().unionAndStore(key, otherKey, storeKey);
            logger.info("zUnionStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取两个(key对应的)ZSet的并集, 并将结果add到storeKey对应的ZSet中。
         * <p>
         * 注: 和set一样，zset中item是唯一的，在多个zset进行Union时, 处理相同的item时，score的值会变为对应的score之和，如：
         * SortedSets.zAdd("name1", "a", 1);和SortedSets.zAdd("name2", "a", 2);
         * 对(name1和name2对应的)zset进行zUnionAndStore之后，新的zset中的项a,对应的score值为3
         * <p>
         * case1: 并集不为空, storeKey不存在，则 会创建对应的storeKey，并将并集添加到(storeKey对应的)ZSet中
         * case2: 并集不为空, storeKey已存在，则 会清除原(storeKey对应的)ZSet中所有的项，然后将并集添加到(storeKey对应的)ZSet中
         * case3: 并集为空, 则不进行下面的操作, 直接返回0
         *
         * @param key 定位其中一个set的键
         * @param otherKeys 定位其它set的键集
         * @param storeKey 定位(要把并集添加到哪个)set的key
         * @return add到(storeKey对应的)ZSet后, 该ZSet对应的size
         * @see <a href="https://redis.io/docs/latest/commands/zunionstore/">zUnionStore</a>
         * @since 2020-03-11 12:26:24
         */
        public static long zUnionStore(String key, Collection<String> otherKeys, String storeKey) {
            logger.info("zUnionStore(...) => key -> {}, otherKeys -> {}, storeKey -> {}", key, otherKeys, storeKey);
            Long result = Lazy.redisTemplate().opsForZSet().unionAndStore(key, otherKeys, storeKey);
            logger.info("zUnionStore(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis映射表（Hash）操作.
     *
     * <p>
     * 提示: 简单的，可以将redis中hash的数据结构看作是 Map<String, Map<HK, HV>>
     * 提示: redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例一).png
     * redis中String的数据结构可参考resources/data-structure/Hash(散列)的数据结构(示例二).png
     *
     * @author JustryDeng
     * @see <a href="https://redis.io/docs/latest/commands/?group=hash">Hash</a>
     * @since 2020-03-08 23:39:26
     */
    public static final class Hashes {

        private Hashes() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * （批量）删除（key对应的）Hash中的对应field-value
         * <p>
         * 注: 1、若redis中不存在对应的key, 则返回0;
         * 2、若要删除的field，在key对应的hash中不存在，则count不会+1, 如:
         * RedisUtil.Hashes.hSet("ds", "name", "邓沙利文");
         * RedisUtil.Hashes.hSet("ds", "birthday", "1994-02-05");
         * RedisUtil.Hashes.hSet("ds", "hobby", "女");
         * 则调用RedisUtil.Hashes.hDel("ds", "name", "birthday", "hobby", "non-exist-field")
         * 的返回结果为3
         * 注: 若(key对应的)hash中的所有entry都被删除了，那么该key也会被删除
         *
         * @param key 定位hash的key
         * @param fields 定位要删除的field-value的field
         * @return 删除了对应hash中多少个entry
         * @see <a href="https://redis.io/docs/latest/commands/hdel/">hDel</a>
         * @since 2020-03-09 09:37:47
         */
        public static long hDel(String key, Object... fields) {
            logger.info("hDel(...) => key -> {}, fields -> {}", key, fields);
            if (fields == null || fields.length == 0) {
                //throw new IllegalArgumentException("请指定要删除的字段：fields");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForHash().delete(key, fields);
            logger.info("hDel(...) => result -> {}", result);
            return result;
        }

        /**
         * 查看(key对应的)hash中，是否存在指定的field
         * <p>
         * 注: 若redis中不存在key,则返回false。
         * 注: 若key对应的hash中不存在对应的field, 也会返回false。
         *
         * @param key 定位hash的key
         * @param field 定位hash中entry的field
         * @return hash中是否存在指定的field
         * @see <a href="https://redis.io/docs/latest/commands/hexists/">hExists</a>
         * @since 2020-03-09 9:51:55
         */
        public static boolean hExists(String key, String field) {
            logger.info("hExists(...) => key -> {}, fields -> {}", key, field);
            Boolean result = Lazy.redisTemplate().opsForHash().hasKey(key, field);
            logger.info("hExists(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 获取到key对应的hash里面的对应字段的值
         * <p>
         * 注：若redis中不存在对应的key，则返回null。
         * 若key对应的hash中不存在对应的field，也会返回null。
         *
         * @param key 定位hash的key
         * @param field 定位hash里面的value的field
         * @return key对应的hash里的field对应的value值
         * @see <a href="https://redis.io/docs/latest/commands/hget/">hGet</a>
         * @since 2020-03-09 9:09:30
         */
        public static Object hGet(String key, String field) {
            logger.info("hGet(...) => key -> {}, field -> {}", key, field);
            Object result = Lazy.redisTemplate().opsForHash().get(key, field);
            logger.info("hGet(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取到key对应的hash(即: 获取到key对应的Map<HK, HV>)
         * <p>
         * 注: 若redis中不存在对应的key, 则返回一个没有任何entry的空的Map(，而不是返回null)。
         *
         * @param key 定位hash的key
         * @return key对应的hash。
         * @see <a href="https://redis.io/docs/latest/commands/hgetall/">hGetAll</a>
         * @since 2020-03-09 9:09:30
         */
        public static Map<Object, Object> hGetAll(String key) {
            logger.info("hGetAll(...) => key -> {}", key);
            Map<Object, Object> result = Lazy.redisTemplate().opsForHash().entries(key);
            logger.info("hGetAll(...) => result -> {}", result);
            return result;
        }

        /**
         * 增/减(hash中的某个value值) 整数
         * <p>
         * 注: 负数则为减。
         * 注: 若key不存在，那么会自动创建对应的hash,并创建对应的field、value,value的初始值为increment。
         * 注: 若field不存在，那么会自动创建对应的value,value的初始值为increment。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         *
         * @param key 用于定位hash的key
         * @param field 用于定位value的field
         * @param increment 增加多少
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/hincrby/">hIncrBy</a>
         * @since 2020-03-09 10:09:28
         */
        public static long hIncrBy(String key, Object field, long increment) {
            logger.info("hIncrBy(...) => key -> {}, field -> {}, increment -> {}",
                    key, field, increment);
            Long result = Lazy.redisTemplate().opsForHash().increment(key, field, increment);
            logger.info("hIncrBy(...) => result -> {}", result);
            return result;
        }

        /**
         * 增/减(hash中的某个value值) 浮点数
         * <p>
         * 注: 负数则为减。
         * 注: 若key不存在，那么会自动创建对应的hash,并创建对应的field、value,value的初始值为increment。
         * 注: 若field不存在，那么会自动创建对应的value,value的初始值为increment。
         * 注: 若key对应的value值不支持增/减操作(即: value不是数字)，那么会
         * 抛出org.springframework.data.redis.RedisSystemException
         * 注: 因为是浮点数，所以可能会和{@link Strings#incrByFloat(String, double)}一样，出现精度问题。
         * 追注: 本人简单测试了几组数据，暂未出现精度问题。
         *
         * @param key 用于定位hash的key
         * @param field 用于定位value的field
         * @param increment 增加多少
         * @return 增加后的总值。
         * @throws RedisSystemException key对应的value值不支持增/减操作时
         * @see <a href="https://redis.io/docs/latest/commands/hincrbyfloat/">hIncrByFloat</a>
         * @since 2020-03-09 10:09:28
         */
        public static double hIncrByFloat(String key, Object field, double increment) {
            logger.info("hIncrByFloat(...) => key -> {}, field -> {}, increment -> {}",
                    key, field, increment);
            Double result = Lazy.redisTemplate().opsForHash().increment(key, field, increment);
            logger.info("hIncrByFloat(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取(key对应的)hash中的所有field
         * <p>
         * 注: 若key不存在，则返回的是一个空的Set(，而不是返回null)
         *
         * @param key 定位hash的key
         * @return hash中的所有field
         * @see <a href="https://redis.io/docs/latest/commands/hkeys/">hKeys</a>
         * @since 2020-03-09 10:30:13
         */
        public static Set<Object> hKeys(String key) {
            logger.info("hKeys(...) => key -> {}", key);
            Set<Object> result = Lazy.redisTemplate().opsForHash().keys(key);
            logger.info("hKeys(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取（key对应的）hash中的所有entry的数量
         * <p>
         * 注: 若redis中不存在对应的key, 则返回值为0
         *
         * @param key 定位hash的key
         * @return (key对应的)hash中, entry的个数
         * @see <a href="https://redis.io/docs/latest/commands/hlen/">hLen</a>
         * @since 2020-03-09 10:41:01
         */
        public static long hLen(String key) {
            logger.info("hLen(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForHash().size(key);
            logger.info("hLen(...) => result -> {}", result);
            return result;
        }

        /**
         * 批量获取(key对应的)hash中的field的value
         * <p>
         * 注: 若hash中对应的field不存在，那么返回的对应的value值为null
         * 注: redis中key不存在，那么返回的List中，每个元素都为null。
         * 追注: 这个List本身不为null, size也不为0，只是每个list中的每个元素为null而已。
         *
         * @param key 定位hash的key
         * @param fields 需要获取的hash中的字段集
         * @return hash中对应fields的对应value集
         * @see <a href="https://redis.io/docs/latest/commands/hmget/">hMGet</a>
         * @since 2020-03-09 9:25:38
         */
        public static List<Object> hMGet(String key, Collection<Object> fields) {
            logger.info("hMGet(...) => key -> {}, fields -> {}", key, fields);
            List<Object> result = Lazy.redisTemplate().opsForHash().multiGet(key, fields);
            logger.info("hMGet(...) => result -> {}", result);
            return result;
        }

        /**
         * 向key对应的hash中，增加hashPairs(即: 批量增加entry集)
         * <p>
         * 注: 同一个hash里面，若已存在相同的field，那么此操作将丢弃原来的field-value，
         * 而使用新的field-value
         *
         * @param key 定位hash的key
         * @param values 要向hash中增加的键值对集
         * @see <a href="https://redis.io/docs/latest/commands/hmset/">hMSet</a>
         * @since 2020-03-08 23:49:52
         */
        public static void hMSet(String key, Map<String, String> values) {
            logger.info("hMSet(...) => key -> {}, values -> {}", key, values);
            Lazy.redisTemplate().opsForHash().putAll(key, values);
        }

        /**
         * 从hash中随机获取一个字段
         *
         * @param key 定位hash的key
         * @return 字段的名称
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/hrandfield/">hRandField</a>
         * @since 2023-12-02 18:46
         */
        public static Object hRandField(String key) {
            logger.info("hRandField(...) => key -> {}", key);
            Object result = Lazy.redisTemplate().opsForHash().randomKey(key);
            logger.info("hRandField(...) => result -> {}", result);
            return result;
        }

        /**
         * 从hash中随机获取指定个数的字段
         *
         * @param key 定位hash的key
         * @param count 获取元素个数
         * @return 字段的名称
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/hrandfield/">hRandField</a>
         * @since 2023-12-02 18:52
         */
        public static List<Object> hRandField(String key, long count) {
            logger.info("hRandField(...) => key -> {}, count -> {}", key, count);
            List<Object> result = Lazy.redisTemplate().opsForHash().randomKeys(key, count);
            logger.info("hRandField(...) => result -> {}", result);
            return result;
        }

        /**
         * 从hash中随机获取一个字段
         *
         * @param key 定位hash的key
         * @return 字段与值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/hrandfieldentry/">hRandFieldEntry</a>
         * @since 2023-12-02 18:54
         */
        public static Map.Entry<Object, Object> hRandFieldEntry(String key) {
            logger.info("hRandFieldEntry(...) => key -> {}", key);
            Map.Entry<Object, Object> result = Lazy.redisTemplate().opsForHash().randomEntry(key);
            logger.info("hRandFieldEntry(...) => result -> {}", result);
            return result;
        }

        /**
         * 从hash中随机获取指定个数的字段
         *
         * @param key 定位hash的key
         * @param count 获取元素个数
         * @return 字段与值
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/hrandfieldentry/">hRandFieldEntry</a>
         * @since 2023-12-02 18:56
         */
        public static Map<Object, Object> hRandFieldEntry(String key, long count) {
            logger.info("hRandFieldEntry(...) => key -> {}, count -> {}", key, count);
            Map<Object, Object> result = Lazy.redisTemplate().opsForHash().randomEntries(key, count);
            logger.info("hRandFieldEntry(...) => result -> {}", result);
            return result;
        }

        /**
         * 根据options匹配到(key对应的)hash中的对应的field，并返回对应的entry集
         *
         * <p>
         * 注: ScanOptions实例的创建方式举例:
         * 1、ScanOptions.NONE
         * 2、ScanOptions.scanOptions().match("n??e").build()
         *
         * @param key 定位hash的key
         * @param options 匹配field的条件
         * 注: ScanOptions.NONE表示全部匹配。
         * 注: ScanOptions.scanOptions().match(pattern).build()表示按照pattern匹配,
         * 其中pattern中可以使用通配符 * ? 等,
         * * 表示>=0个字符
         * ？ 表示有且只有一个字符
         * 此处的匹配规则与{@link Keys#keys(String)}处的一样。
         * @return 匹配到的(key对应的)hash中的entry
         * @see <a href="https://redis.io/docs/latest/commands/hscan/">hScan</a>
         * @see Cursor#close()
         * @since 2020-03-09 10:49:27
         */
        public static Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
            logger.info("hScan(...) => key -> {}, options -> {}", key, options.toOptionString());
            Cursor<Map.Entry<Object, Object>> cursor = Lazy.redisTemplate().opsForHash().scan(key, options);
            logger.info("hScan(...) => cursor -> {}", JacksonUtils.objToJson(cursor));
            return cursor;
        }

        /**
         * 向key对应的hash中，增加一个键值对field-value
         * <p>
         * 注：同一个hash里面，若已存在相同的field，那么此操作将丢弃原来的field-value，而使用新的field-value。
         *
         * @param key 定位hash的key
         * @param field 要向hash中增加的键值对里的 键
         * @param value 要向hash中增加的键值对里的 值
         * @see <a href="https://redis.io/docs/latest/commands/hset/">hSet</a>
         * @since 2020-03-08 23:49:52
         */
        public static void hSet(String key, String field, String value) {
            logger.info("hSet(...) => key -> {}, field -> {}, value -> {}", key, field, value);
            Lazy.redisTemplate().opsForHash().put(key, field, value);
        }

        /**
         * 当key对应的hash中，不存在field时，才（向key对应的hash中）增加field-value，
         * 否则，不进行任何操作
         *
         * @param key 定位hash的key
         * @param field 要向hash中增加的键值对里的 键
         * @param value 要向hash中增加的键值对里的 值
         * @return 操作是否成功
         * @see <a href="https://redis.io/docs/latest/commands/hsetnx/">hSetNX</a>
         * @since 2020-03-08 23:49:52
         */
        public static boolean hSetNX(String key, String field, String value) {
            logger.info("hSetNX(...) => key -> {}, field -> {}, value -> {}", key, field, value);
            Boolean result = Lazy.redisTemplate().opsForHash().putIfAbsent(key, field, value);
            logger.info("hSetNX(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 获取（key对应的）hash中（field指定的）entry的长度
         *
         * @param key 用于定位hash的key
         * @param field 用于定位value的field
         * @return Hash数据长度
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/hstrlen/">hStrLen</a>
         * @since 2023-11-24 22:51
         */
        public static long hStrLen(String key, Object field) {
            logger.info("hStrLen(...) => key -> {}, field -> {}", key, field);
            Long result = Lazy.redisTemplate().opsForHash().lengthOfValue(key, field);
            logger.info("hStrLen(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取(key对应的)hash中的所有value
         * <p>
         * 注: 若key不存在，则返回的是一个空的List(，而不是返回null)
         *
         * @param key 定位hash的key
         * @return hash中的所有value
         * @see <a href="https://redis.io/docs/latest/commands/hvals/">hVals</a>
         * @since 2020-03-09 10:30:13
         */
        public static List<Object> hVals(String key) {
            logger.info("hVals(...) => key -> {}", key);
            List<Object> result = Lazy.redisTemplate().opsForHash().values(key);
            logger.info("hVals(...) => result -> {}", result);
            return result;
        }

    }

    /**
     * Redis流（Stream，消息队列）操作.
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=stream">Stream</a>
     */
    public static final class Streams {

        private Streams() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 确认消息已消费
         *
         * @param key 定位消息的key
         * @param group 消费组
         * @param recordIds 消息ID
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xack/">xAck</a>
         * @since 2023-11-24 23:05
         */
        public static long xAck(String key, String group, String... recordIds) {
            logger.info("xAck(...) => key -> {}, group -> {}, recordIds -> {}", key, group, recordIds);
            if (recordIds == null || recordIds.length == 0) {
                //throw new IllegalArgumentException("请指定要确认的消息ID：recordIds");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForStream().acknowledge(key, group, recordIds);
            logger.info("xAck(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 追加消息
         *
         * @param key 定位消息的key
         * @param field Field
         * @param value Value
         * @return 消息ID
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xadd/">xAdd</a>
         * @since 2023-11-24 23:20
         */
        public static String xAdd(String key, String field, Object value) {
            return xAdd(key, Collections.singletonMap(field, value));
        }

        /**
         * 追加消息
         *
         * @param key 定位消息的key
         * @param content 消息
         * @return 消息ID
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xadd/">xAdd</a>
         * @since 2023-11-24 23:15
         */
        public static String xAdd(String key, Map<String, Object> content) {
            logger.info("xAdd(...) => key -> {}, content -> {}", key, content);
            RecordId result = Lazy.redisTemplate().opsForStream().add(key, content);
            logger.info("xAdd(...) => result -> {}", result);
            return result != null ? result.getValue() : null;
        }

        /// xAutoClaim  https://redis.io/docs/latest/commands/xautoclaim/

        /**
         * 改变待处理消息的所有权（认领消息）
         *
         * @param key 定位消息的key
         * @param consumerGroup 消息消费者组
         * @param newOwner 消息消费者
         * @param minIdleTime 已被读取时长
         * @param recordIds 消息ID
         * @return 认领到的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xclaim/">xClaim</a>
         * @see #xClaim(String, String, String, RedisStreamCommands.XClaimOptions)
         * @since 2023-12-02 19:51
         */
        public static List<MapRecord<String, Object, Object>> xClaim(String key, String consumerGroup,
                String newOwner, Duration minIdleTime, RecordId... recordIds) {
            logger.info("xClaim(...) => key -> {}, group -> {}, consumer -> {}, minIdleTime -> {}, recordIds -> {}",
                    key, consumerGroup, newOwner, minIdleTime, recordIds);
            if (recordIds == null || recordIds.length == 0) {
                //throw new IllegalArgumentException("请指定要认领的消息ID：recordIds");
                return Collections.emptyList();
            }
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .claim(key, consumerGroup, newOwner, minIdleTime, recordIds);
            logger.info("xClaim(...) => result -> {}", result);
            return result;
        }

        /**
         * 改变待处理消息的所有权（认领消息）
         *
         * @param key 定位消息的key
         * @param consumerGroup 消息消费者组
         * @param newOwner 消息消费者
         * @param xClaimOptions 认领选项
         * @return 认领到的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xclaim/">xClaim</a>
         * @see #xClaim(String, String, String, Duration, RecordId...)
         * @since 2023-12-02 19:44
         */
        public static List<MapRecord<String, Object, Object>> xClaim(String key, String consumerGroup,
                String newOwner, RedisStreamCommands.XClaimOptions xClaimOptions) {
            logger.info("xClaim(...) => key -> {}, group -> {}, consumer -> {}, options -> {}",
                    key, consumerGroup, newOwner, xClaimOptions);
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .claim(key, consumerGroup, newOwner, xClaimOptions);
            logger.info("xClaim(...) => result -> {}", result);
            return result;
        }

        /**
         * 从指定流中移除指定的条目，并返回成功删除的条目的数量
         *
         * @param key 定位消息的key
         * @param recordIds 消息ID
         * @return 移除消息的数量
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xdel/">xDel</a>
         * @since 2023-12-02 19:49
         */
        public static long xDel(String key, String... recordIds) {
            logger.info("xDel(...) => key -> {}, recordIds -> {}", key, recordIds);
            if (recordIds == null || recordIds.length == 0) {
                //throw new IllegalArgumentException("请指定要移除的消息ID：recordIds");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForStream().delete(key, recordIds);
            logger.info("xDel(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从指定流中移除指定的条目，并返回成功删除的条目的数量
         *
         * @param key 定位消息的key
         * @param recordIds 消息ID
         * @return 移除消息的数量
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xdel/">xDel</a>
         * @since 2023-12-02 19:58
         */
        public static long xDel(String key, RecordId... recordIds) {
            logger.info("xDel(...) => key -> {}, recordIds -> {}", key, recordIds);
            if (recordIds == null || recordIds.length == 0) {
                //throw new IllegalArgumentException("请指定要移除的消息ID：recordIds");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForStream().delete(key, recordIds);
            logger.info("xDel(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 创建消费者组
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @return 结果
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xgroup-create/">xGroupCreate</a>
         * @since 2023-12-02 20:02
         */
        public static String xGroupCreate(String key, String group) {
            logger.info("xGroupCreate(...) => key -> {}, group -> {}", key, group);
            String result = Lazy.redisTemplate().opsForStream().createGroup(key, group);
            logger.info("xGroupCreate(...) => result -> {}", result);
            return result;
        }

        /**
         * 创建消费者组
         *
         * @param key 定位消息的key
         * @param readOffset 设置读取消息的偏移量
         * @param group 消息消费者组
         * @return 结果
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xgroup-create/">xGroupCreate</a>
         * @since 2023-12-02 20:06
         */
        public static String xGroupCreate(String key, ReadOffset readOffset, String group) {
            logger.info("xGroupCreate(...) => key -> {}, readOffset -> {}, group -> {}", key, readOffset, group);
            String result = Lazy.redisTemplate().opsForStream().createGroup(key, readOffset, group);
            logger.info("xGroupCreate(...) => result -> {}", result);
            return result;
        }

        /// xGroupCreateConsumer  https://redis.io/docs/latest/commands/xgroup-createconsumer/

        /**
         * 删除消费者组
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @param consumer 消息消费者
         * @return 删除是否成功
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xgroup-delconsumer/">xGroupDelConsumer</a>
         * @since 2023-12-02 20:13
         */
        public static boolean xGroupDelConsumer(String key, String group, String consumer) {
            logger.info("xGroupDelConsumer(...) => key -> {}, group -> {}, consumer -> {}", key, group, consumer);
            Boolean result = Lazy.redisTemplate().opsForStream().deleteConsumer(key, Consumer.from(group, consumer));
            logger.info("xGroupDelConsumer(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 销毁消费者组
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @return 销毁是否成功
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xgroup-destroy/">xGroupDestroy</a>
         * @since 2023-12-02 20:17
         */
        public static boolean xGroupDestroy(String key, String group) {
            logger.info("xGroupDestroy(...) => key -> {}, group -> {}", key, group);
            Boolean result = Lazy.redisTemplate().opsForStream().destroyGroup(key, group);
            logger.info("xGroupDestroy(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        // 设置消息消费者组的偏移量
        /// xGroupSetId  https://redis.io/docs/latest/commands/xgroup-setid/

        /**
         * 获取消息消费者的信息
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @return 消息消费者的信息
         * @see <a href="https://redis.io/docs/latest/commands/xinfo-consumers/">XINFO CONSUMERS</a>
         * @since 2023-12-02 20:23
         */
        public static StreamInfo.XInfoConsumers xInfoConsumers(String key, String group) {
            logger.info("xInfoConsumers(...) => key -> {}, group -> {}", key, group);
            StreamInfo.XInfoConsumers result = Lazy.redisTemplate().opsForStream().consumers(key, group);
            logger.info("xInfoConsumers(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取消息消费者组的信息
         *
         * @param key 定位消息的key
         * @return 消息消费者组的信息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xinfo-groups/">XINFO GROUPS</a>
         * @since 2023-12-02 20:27
         */
        public static StreamInfo.XInfoGroups xInfoGroups(String key) {
            logger.info("xInfoGroups(...) => key -> {}", key);
            StreamInfo.XInfoGroups result = Lazy.redisTemplate().opsForStream().groups(key);
            logger.info("xInfoGroups(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取消息的信息
         *
         * @param key 定位消息的key
         * @return 消息的信息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xinfo-stream/">XINFO STREAM</a>
         * @since 2023-12-02 20:31
         */
        public static StreamInfo.XInfoStream xInfoStream(String key) {
            logger.info("xInfoStream(...) => key -> {}", key);
            StreamInfo.XInfoStream result = Lazy.redisTemplate().opsForStream().info(key);
            logger.info("xInfoStream(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取消息条数
         *
         * @param key 定位消息的key
         * @return 消息条数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xlen/">XLEN</a>
         * @since 2023-12-02 20:34
         */
        public static long xLen(String key) {
            logger.info("xLen(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForStream().size(key);
            logger.info("xLen(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取未处理的消息
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @return 未处理的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xpending/">xPending</a>
         * @since 2023-12-02 20:43
         */
        public static PendingMessagesSummary xPending(String key, String group) {
            logger.info("xPending(...) => key -> {}, group -> {}", key, group);
            PendingMessagesSummary result = Lazy.redisTemplate().opsForStream().pending(key, group);
            logger.info("xPending(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取未处理的消息
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @param consumer 消息消费者
         * @return 未处理的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xpending/">xPending</a>
         * @since 2023-12-02 20:47
         */
        public static PendingMessages xPending(String key, String group, String consumer) {
            logger.info("xPending(...) => key -> {}, group -> {}, consumer -> {}", key, group, consumer);
            PendingMessages result = Lazy.redisTemplate().opsForStream()
                    .pending(key, Consumer.from(group, consumer));
            logger.info("xPending(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取未处理的消息
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @param range 获取区间
         * @param count 获取消息数量
         * @return 未处理的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xpending/">xPending</a>
         * @since 2023-12-02 20:51
         */
        public static PendingMessages xPending(String key, String group, Range<?> range, long count) {
            logger.info("xPending(...) => key -> {}, group -> {}, range -> {}, count -> {}",
                    key, group, range, count);
            PendingMessages result = Lazy.redisTemplate().opsForStream()
                    .pending(key, group, range, count);
            logger.info("xPending(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取未处理的消息
         *
         * @param key 定位消息的key
         * @param group 消息消费者组
         * @param consumer 消息消费者
         * @param range 获取区间
         * @param count 获取消息数量
         * @return 未处理的消息
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xpending/">xPending</a>
         * @since 2023-12-02 20:55
         */
        public static PendingMessages xPending(String key, String group, String consumer,
                Range<?> range, long count) {
            logger.info("xPending(...) => key -> {}, group -> {}, consumer -> {}, range -> {}, count -> {}",
                    key, group, consumer, range, count);
            PendingMessages result = Lazy.redisTemplate().opsForStream()
                    .pending(key, Consumer.from(group, consumer), range, count);
            logger.info("xPending(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取指定范围内的成员，并按照成员属性的值进行排序
         *
         * @param key 定位消息的key
         * @param range 指定范围
         * @return 成员列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xrange/">xRange</a>
         * @since 2023-12-02 21:02
         */
        public static List<MapRecord<String, Object, Object>> xRange(String key, Range<String> range) {
            logger.info("xRange(...) => key -> {}, range -> {}", key, range);
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .range(key, range);
            logger.info("xRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取指定范围内的成员，并按照成员属性的值进行排序
         *
         * @param key 定位消息的key
         * @param range 指定范围
         * @param limit 分页参数
         * @return 成员列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xrange/">xRange</a>
         * @since 2023-12-02 21:04
         */
        public static List<MapRecord<String, Object, Object>> xRange(String key, Range<String> range, Limit limit) {
            logger.info("xRange(...) => key -> {}, range -> {}", key, range);
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .range(key, range, limit);
            logger.info("xRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 消费消息
         *
         * @param key 定位消息的key
         * @param count 指定获取消息数量
         * @return 消息列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xread/">xRead</a>
         * @since 2023-12-03 17:38
         */
        public static List<MapRecord<String, Object, Object>> xRead(String key, long count) {
            logger.info("xRead(...) => key -> {}, count -> {}", key, count);
            @SuppressWarnings("unchecked")
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .read(StreamReadOptions.empty().count(count),
                            StreamOffset.create(key, ReadOffset.lastConsumed()));
            logger.info("xRead(...) => result -> {}", result);
            return result;
        }

        /**
         * 消费消息（阻塞等待）
         *
         * @param key 定位消息的key
         * @param count 指定获取消息数量
         * @param timeout 阻塞等待时间
         * @return 消息列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xread/">xRead</a>
         * @since 2023-12-03 17:44
         */
        public static List<MapRecord<String, Object, Object>> xRead(String key, long count, Duration timeout) {
            logger.info("xRead(...) => key -> {}, count -> {}, timeout -> {}", key, count, timeout);
            @SuppressWarnings("unchecked")
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .read(StreamReadOptions.empty().block(timeout).count(count),
                            StreamOffset.create(key, ReadOffset.lastConsumed()));
            logger.info("xRead(...) => result -> {}", result);
            return result;
        }

        /**
         * 读取消费者组中的消息
         *
         * @param key 定位消息的key
         * @param count 指定获取消息数量
         * @param group 消息消费者组
         * @param consumer 消息消费者
         * @return 消息列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xreadgroup/">xReadGroup</a>
         * @since 2023-12-03 19:40
         */
        public static List<MapRecord<String, Object, Object>> xReadGroup(String key, long count,
                String group, String consumer) {
            logger.info("xReadGroup(...) => key -> {}, count -> {}, group -> {}, consumer -> {}",
                    key, count, group, consumer);
            @SuppressWarnings("unchecked")
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .read(Consumer.from(group, consumer),
                            StreamReadOptions.empty().count(count),
                            StreamOffset.create(key, ReadOffset.lastConsumed()));
            logger.info("xReadGroup(...) => result -> {}", result);
            return result;
        }

        /**
         * 读取消费者组中的消息（阻塞等待）
         *
         * @param key 定位消息的key
         * @param count 指定获取消息数量
         * @param group 消息消费者组
         * @param consumer 消息消费者
         * @param timeout 阻塞等待时间
         * @return 消息列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xreadgroup/">xReadGroup</a>
         * @since 2023-12-03 19:45
         */
        public static List<MapRecord<String, Object, Object>> xReadGroup(String key, long count,
                String group, String consumer, Duration timeout) {
            logger.info("xReadGroup(...) => key -> {}, count -> {}, group -> {}, consumer -> {}",
                    key, count, group, consumer);
            @SuppressWarnings("unchecked")
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .read(Consumer.from(group, consumer),
                            StreamReadOptions.empty().block(timeout).count(count),
                            StreamOffset.create(key, ReadOffset.lastConsumed()));
            logger.info("xReadGroup(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取指定范围内的成员，并按照成员属性的值进行逆序排序
         *
         * @param key 定位消息的key
         * @param range 指定范围
         * @return 成员列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xrevrange/">xRevRange</a>
         * @see #xRevRange(String, Range, Limit)
         * @see #xRange(String, Range)
         * @since 2023-12-03 20:00
         */
        public static List<MapRecord<String, Object, Object>> xRevRange(String key, Range<String> range) {
            logger.info("xRevRange(...) => key -> {}, range -> {}", key, range);
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .reverseRange(key, range);
            logger.info("xRevRange(...) => result -> {}", result);
            return result;
        }

        /**
         * 获取指定范围内的成员，并按照成员属性的值进行逆序排序
         *
         * @param key 定位消息的key
         * @param range 指定范围
         * @param limit 分页参数
         * @return 成员列表
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xrevrange/">xRevRange</a>
         * @see #xRevRange(String, Range)
         * @see #xRange(String, Range, Limit)
         * @since 2023-12-03 20:03
         */
        public static List<MapRecord<String, Object, Object>> xRevRange(String key, Range<String> range, Limit limit) {
            logger.info("xRevRange(...) => key -> {}, range -> {}, limit -> {}", key, range, limit);
            List<MapRecord<String, Object, Object>> result = Lazy.redisTemplate().opsForStream()
                    .reverseRange(key, range, limit);
            logger.info("xRevRange(...) => result -> {}", result);
            return result;
        }

        /// xSetId  https://redis.io/docs/latest/commands/xsetid/

        /**
         * 将流裁剪为指定数量的项目，如有需要，将驱逐旧的项目（ID较小的项目）
         *
         * @param key 定位消息的key
         * @param count 限制长度
         * @return 从流中删除的条目数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xtrim/">xTrim</a>
         * @since 2023-12-03 20:17
         */
        public static long xTrim(String key, long count) {
            return xTrim(key, count, false);
        }

        /**
         * 将流裁剪为指定数量的项目，如有需要，将驱逐旧的项目（ID较小的项目）
         *
         * @param key 定位消息的key
         * @param count 限制长度
         * @param approximateTrimming 是否以近似的方式执行
         * @return 从流中删除的条目数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/xtrim/">xTrim</a>
         * @since 2023-12-03 20:10
         */
        public static long xTrim(String key, long count, boolean approximateTrimming) {
            logger.info("xTrim(...) => key -> {}, count -> {}, approximateTrimming -> {}", key, count, approximateTrimming);
            Long result = Lazy.redisTemplate().opsForStream().trim(key, count, approximateTrimming);
            logger.info("xTrim(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis地理位置（Geospatial Indices）操作.
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=geo">Geo</a>
     * @since 2024-04-18 21:42
     */
    public static final class Geo {

        private Geo() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 添加一个地理位置坐标（使用ZSet存储）
         *
         * @param key 键
         * @param lng 经度
         * @param lat 纬度
         * @param member 位置名称
         * @return 添加位置的个数
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/geoadd/">geoAdd</a>
         * @see SortedSets#zRem(String, Object...)
         * @since 2024-04-18 21:49
         */
        public static long geoAdd(String key, double lng, double lat, String member) {
            logger.info("geoAdd(...) => key -> {}, lng -> {}, lat -> {}, member -> {}", key, lng, lat, member);
            Long result = Lazy.redisTemplate().opsForGeo().add(key, new Point(lng, lat), member);
            logger.info("geoAdd(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 计算键内两个地理位置的距离（单位：千米）
         *
         * @param key 键
         * @param member1 位置1
         * @param member2 位置2
         * @return 距离（单位：千米）
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/geodist/">geoDist</a>
         * @since 2024-04-18 22:12
         */
        public static double geoDist(String key, String member1, String member2) {
            logger.info("geoDist(...) => key -> {}, member1 -> {}, member1 -> {}", key, member1, member2);
            Distance result = Lazy.redisTemplate().opsForGeo().distance(key, member1, member2,
                    RedisGeoCommands.DistanceUnit.KILOMETERS);
            logger.info("geoDist(...) => result -> {}", result);
            return result != null ? result.getValue() : -1.0d;
        }

        /**
         * @param key 键
         * @param members 地理位置名称
         * @return
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/geohash/">geoHash</a>
         * @since 2024-04-18 22:27
         */
        public static List<String> geoHash(final String key, final String... members) {
            logger.info("geoHash(...) => key -> {}, members -> {}", key, members);
            if (members == null || members.length == 0) {
                //throw new IllegalArgumentException("请指定要获取GeoHash的位置名称：members");
                return Collections.emptyList();
            }
            List<String> result = Lazy.redisTemplate().opsForGeo().hash(key, members);
            logger.info("geoHash(...) => result -> {}", result);
            return result != null ? result : Collections.emptyList();
        }

    }

    /**
     * Redis位图（Bitmap）操作.
     *
     * <p>Bitmap，即位图，是一串连续的二进制数组（0或1），可以通过偏移量（offset）定位元素。Bitmap通过最小的单位Bit来进行0|1的设置，
     * 表示某个元素的值或者状态，时间复杂度为O(1).</p>
     * <p>由于Bit是计算机中最小的单位，使用它进行储存将非常节省空间，特别适合一些数据量大且使用<b>二值统计的场景</b>。</p>
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=bitmap">Bitmap</a>
     */
    public static final class Bitmaps {

        private Bitmaps() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 统计字符串被设置为1的Bit数量
         *
         * @param key 定位value的key
         * @return Bit数量
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitcount/">bitCount</a>
         * @since 2023-12-03 21:36
         */
        public static long bitCount(String key) {
            logger.info("bitCount(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().bitCount(key.getBytes(StandardCharsets.UTF_8)));
            logger.info("bitCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 统计字符串被设置为1的Bit数量
         *
         * @param key 定位value的key
         * @param start 开始Bit
         * @param end 结束Bit
         * @return Bit数量
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitcount/">bitCount</a>
         * @since 2023-12-03 21:45
         */
        public static long bitCount(String key, long start, long end) {
            logger.info("bitCount(...) => key -> {}, start -> {}, end -> {}", key, start, end);
            Long result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().bitCount(key.getBytes(StandardCharsets.UTF_8), start, end));
            logger.info("bitCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 对指定位片段进行读写
         *
         * @param key 定位value的key
         * @param subCommands 子命令，GET/SET/INCRBY
         * @return 针对子命令给定位置的处理结果
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitfield/">bitField</a>
         * @since 2023-12-03 21:51
         */
        public static List<Long> bitField(String key, BitFieldSubCommands subCommands) {
            logger.info("bitField(...) => key -> {}, subCommands -> {}", key, subCommands);
            List<Long> result = Lazy.redisTemplate().opsForValue().bitField(key, subCommands);
            logger.info("bitField(...) => result -> {}", result);
            return result;
        }

        /**
         * 对一个或多个保存二进制位的字符串 key 进行位元操作，并将结果保存到destKey
         *
         * @param bitOp 位元操作
         * @param destKey 结果保存Key
         * @param key 操作Key
         * @return 保存到 destKey 的字符串的长度，和输入 key 中最长的字符串长度相等
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitop/">bitOp</a>
         * @since 2023-12-04 20:45
         */
        public static long bitOp(RedisStringCommands.BitOperation bitOp, String destKey, String... key) {
            logger.info("bitOp(...) => bitOp -> {}, destKey -> {}, key -> {}", bitOp, destKey, key);
            if (key == null || key.length == 0) {
                logger.info("bitOp(...) => result -> {}", -1L);
                return -1L;
            }
            List<byte[]> keys = new ArrayList<>(key.length);
            for (String str : key) {
                keys.add(str.getBytes(StandardCharsets.UTF_8));
            }
            Long result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().bitOp(bitOp,
                            destKey.getBytes(StandardCharsets.UTF_8),
                            keys.toArray(new byte[0][0])));
            logger.info("bitOp(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 返回字符串里面第一个被设置为1或者0的Bit位置
         *
         * @param key 定位value的key
         * @param bit 查找Bit值（true - 1，false - 0）
         * @return Bit位置
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitpos/">bitPos</a>
         * @since 2023-12-04 20:59
         */
        public static long bitPos(String key, boolean bit) {
            logger.info("bitPos(...) => key -> {}, bit -> {}", key, bit);
            Long result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().bitPos(key.getBytes(StandardCharsets.UTF_8), bit));
            logger.info("bitPos(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 从指定范围内查找字符串里面第一个被设置为1或者0的Bit位置
         *
         * @param key 定位value的key
         * @param bit 查找Bit值（true - 1，false - 0）
         * @param range 查找范围
         * @return Bit位置
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/bitpos/">bitPos</a>
         * @since 2023-12-04 21:03
         */
        public static long bitPos(String key, boolean bit, Range<Long> range) {
            logger.info("bitPos(...) => key -> {}, bit -> {}, range -> {}", key, bit, range);
            Long result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().bitPos(key.getBytes(StandardCharsets.UTF_8), bit, range));
            logger.info("bitPos(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 获取(key对应的)value在二进制下，offset位置的bit值。
         * <p>
         * 注: 当offset的值在(二进制下的value的)索引范围外时, 返回的也是false。
         * <p>
         * 示例:
         * Strings.set("akey", "a");
         * 字符串a, 转换为二进制为01100001
         * 那么getBit("akey", 6)获取到的结果为false。
         *
         * @param key 定位value的key
         * @param offset 定位bit的索引
         * @return offset位置对应的bit的值（true - 1，false - 0）
         * @see <a href="https://redis.io/docs/latest/commands/getbit/">getBit</a>
         * @since 2020-03-08 18:21:10
         */
        public static boolean getBit(String key, long offset) {
            logger.info("getBit(...) => key -> {}, offset -> {}", key, offset);
            Boolean result = Lazy.redisTemplate().opsForValue().getBit(key, offset);
            logger.info("getBit(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

        /**
         * 处理redis中key对应的value值，将第offset位的值，设置为1或0。
         * <p>
         * 说明：在redis中，存储的字符串都是以二级制的进行存在的；如存储的key-value里，值为abc,实际上，
         * 在redis里面存储的是011000010110001001100011，前8为对应，中间8为对应b，后面8位对应c。
         * 示例：这里如果setBit(key, 6, true)的话，就是将索引位置6的那个数，设置值为1，值就变成
         * 了011000110110001001100011
         * 追注：offset即index,从0开始。
         * <p>
         * 注：参数value为true, 则设置为1；参数value为false, 则设置为0。
         * <p>
         * 注：若redis中不存在对应的key,那么会自动创建新的。
         * 注：offset可以超过value在二进制下的索引长度。
         *
         * @param key 定位value的key
         * @param offset 要改变的bit的索引
         * @param value 设置Bit值（true - 1，false - 0）
         * @return set是否成功
         * @see <a href="https://redis.io/docs/latest/commands/setbit/">setBit</a>
         * @since 2020-03-08 16:30:37
         */
        public static boolean setBit(String key, long offset, boolean value) {
            logger.info("setBit(...) => key -> {}, offset -> {}, value -> {}", key, offset, value);
            Boolean result = Lazy.redisTemplate().opsForValue().setBit(key, offset, value);
            logger.info("setBit(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

    }

    /**
     * Redis基数计数概率算法.
     *
     * @see <a href="https://redis.io/docs/latest/commands/?group=hyperloglog">HyperLogLog</a>
     */
    public static final class HyperLogLogs {

        private HyperLogLogs() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 添加元素到HLL中
         *
         * @param key 键
         * @param element 元素
         * @return 如果一个HLL估计的近似基数在执行命令过程中发了变化，返回1，否则返回0
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/pfadd/">pfAdd</a>
         * @since 2023-11-27 23:40
         */
        public static long pfAdd(String key, String element) {
            logger.info("pfAdd(...) => key -> {}, element -> {}", key, element);
            Long result = Lazy.redisTemplate().opsForHyperLogLog().add(key, element);
            logger.info("pfAdd(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 返回HLL中的近似基数
         *
         * @param key 键
         * @return 近似基数，如果key不存在，则返回0
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/pfcount/">pfCount</a>
         * @since 2023-11-27 23:49
         */
        public static long pfCount(String key) {
            logger.info("pfCount(...) => key -> {}", key);
            Long result = Lazy.redisTemplate().opsForHyperLogLog().size(key);
            logger.info("pfCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

        /**
         * 返回HLL中的近似基数
         *
         * @param keys 键
         * @return 近似基数，如果key不存在，则返回0
         * @author liym
         * @see <a href="https://redis.io/docs/latest/commands/pfcount/">pfCount</a>
         * @since 2023-11-27 23:49
         */
        public static long pfCount(String... keys) {
            logger.info("pfCount(...) => keys -> {}", Arrays.toString(keys));
            if (keys == null || keys.length == 0) {
                //throw new IllegalArgumentException("请指定键：keys");
                return 0L;
            }
            Long result = Lazy.redisTemplate().opsForHyperLogLog().size(keys);
            logger.info("pfCount(...) => result -> {}", result);
            return result != null ? result : -1L;
        }

    }

    /**
     * Redis分布式锁.
     * <p>
     * 使用方式（示例）：
     * <pre>
     * String lockKey = "autocommit:product";
     * String lockVal = UUID.randomUUID().toString();
     * boolean locked = false;
     * try {
     *     // 非阻塞获取（锁的最大存活时间采用默认值）
     *     locked = RedisUtils.Locks.tryLock(lockKey, lockVal);
     *     // 非阻塞获取e.g.
     *     locked = RedisUtils.Locks.tryLock(lockKey, lockVal, 3L, TimeUnit.SECONDS);
     *     // 阻塞获取（锁的最大存活时间采用默认值）
     *     locked = RedisUtils.Locks.tryLockUntilTimeout(lockKey, lockVal, 2000L);
     *     // 阻塞获取
     *     locked = RedisUtils.Locks.tryLockUntilTimeout(lockKey, lockVal, 2L, TimeUnit.SECONDS, 2000L);
     *     // your logic
     *     // ...
     * } finally {
     *     if (locked) {
     *         RedisUtils.Locks.tryUnlock(lockKey, lockVal);
     *     }
     * }
     * </pre>
     * </p>
     *
     * @author JustryDeng
     * @since 2020-03-14 19:23:26
     */
    public static final class Locks {

        /**
         * 分布式锁默认（最大）存活时长
         *
         * @see #DEFAULT_TIMEOUT_UNIT
         */
        public static final long DEFAULT_LOCK_TIMEOUT = 3L;

        /**
         * {@link #DEFAULT_LOCK_TIMEOUT}的单位，秒
         *
         * @see #DEFAULT_LOCK_TIMEOUT
         */
        public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;

        /** lua脚本，保证 释放锁脚本 的原子性（以避免并发场景下，释放了别人的锁） */
        private static final String UNLOCK_LUA_SCRIPT;

        static {
            // 不论lua中0是否代表失败; 对于java的Boolean而言，返回0，则会被解析为false
            UNLOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] "
                    + "then return redis.call('del', KEYS[1]) "
                    + "else return 0 "
                    + "end";
        }

        private Locks() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 获取（分布式）锁
         * <p>
         * 注：获取结果是即时返回的、是非阻塞的。
         *
         * @see Locks#tryLock(String, String, long, TimeUnit)
         */
        public static boolean tryLock(final String key, final String value) {
            return tryLock(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT);
        }

        /**
         * 获取（分布式）锁
         * <p>
         * 若成功，则直接返回，
         * 若失败，则进行重试，直到成功或超时为止。
         * </p>
         * 注：获取结果是阻塞的，要么成功，要么超时，才返回。
         *
         * @param retryTimeoutLimit 重试的超时时长(ms)
         * @return 是否成功
         * @see Locks#tryLock(String, String, long, TimeUnit)
         */
        public static boolean tryLockUntilTimeout(final String key, final String value,
                final long retryTimeoutLimit) {
            return tryLockUntilTimeout(key, value, DEFAULT_LOCK_TIMEOUT, DEFAULT_TIMEOUT_UNIT, retryTimeoutLimit);
        }

        /**
         * 获取（分布式）锁
         * <p>
         * 若成功，则直接返回，
         * 若失败，则进行重试，直到成功或超时为止。
         * </p>
         * 注：获取结果是阻塞的，要么成功，要么超时，才返回。
         *
         * @param retryTimeoutLimit 重试的超时时长(ms)
         * @return 是否成功
         * @see Locks#tryLock(String, String, long, TimeUnit, boolean)
         */
        public static boolean tryLockUntilTimeout(final String key, final String value,
                final long timeout, final TimeUnit unit,
                final long retryTimeoutLimit) {
            logger.info("tryLockUntilTimeout(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}, "
                    + "retryTimeoutLimit -> {}ms", key, value, timeout, unit, retryTimeoutLimit);
            long startTime = Instant.now().toEpochMilli();
            long now = startTime;
            do {
                try {
                    boolean locked = tryLock(key, value, timeout, unit, false);
                    if (locked) {
                        logger.info("tryLockUntilTimeout(...) => consume time -> {}ms, result -> true", now - startTime);
                        return true;
                    }
                } catch (Exception e) {
                    logger.error("tryLockUntilTimeout(...) => try to get lock failure", e);
                }
                now = Instant.now().toEpochMilli();
            } while (now < startTime + retryTimeoutLimit);
            logger.info("tryLockUntilTimeout(...) => consume time -> {}ms, result -> false", now - startTime);
            return false;
        }

        /**
         * 获取（分布式）锁
         * <p>
         * 注：获取结果是即时返回的、是非阻塞的。
         *
         * @see Locks#tryLock(String, String, long, TimeUnit, boolean)
         */
        public static boolean tryLock(final String key, final String value,
                final long timeout, final TimeUnit unit) {
            return tryLock(key, value, timeout, unit, true);
        }

        /**
         * 获取（分布式）锁
         * <p>
         * 注：获取结果是即时返回的、是非阻塞的。
         *
         * @param key 锁名
         * @param value 锁名对应的value
         * 注: value一般采用全局唯一的值，如: requestId、uuid等。
         * 这样，释放锁的时候, 可以再次验证value值,
         * 保证自己上的锁只能被自己释放, 而不会被别人释放。
         * 当然, 如果锁超时时, 会被redis自动删除释放。
         * @param timeout 锁的(最大)存活时长
         * 注: 一般的，获取锁与释放锁 都是成对使用的, 在锁在达到(最大)存活时长之前，都会被主动释放。
         * 但是在某些情况下(如:程序获取锁后,释放锁前,崩了),锁得不到释放, 这时就需要等锁过
         * 了(最大)存活时长后，被redis自动删除清理了。这样就能保证redis中不会留下死数据。
         * @param unit timeout的单位
         * @param recordLog 是否记录日志
         * @return 是否成功
         */
        public static boolean tryLock(final String key, final String value,
                final long timeout, final TimeUnit unit,
                boolean recordLog) {
            if (recordLog) {
                logger.info("tryLock(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",
                        key, value, timeout, unit);
            }
            Boolean result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.stringCommands().set(
                            key.getBytes(StandardCharsets.UTF_8),
                            value.getBytes(StandardCharsets.UTF_8),
                            Expiration.seconds(unit.toSeconds(timeout)),
                            RedisStringCommands.SetOption.SET_IF_ABSENT) // NX
            );
            if (recordLog) {
                logger.info("tryLock(...) => result -> {}", result);
            }
            return Boolean.TRUE.equals(result);
        }

        /**
         * 释放（分布式）锁
         * <p>
         * 注：此方式能（通过value的唯一性）保证自己加的锁，只能被自己释放。
         * 注：锁超时时，也会被redis自动删除释放。
         *
         * @param key 锁名
         * @param value 锁名对应的value
         * @return 释放锁是否成功
         * @since 2020-03-15 17:00:45
         */
        public static boolean tryUnlock(final String key, final String value) {
            logger.info("tryUnlock(...) => key -> {}, value -> {}", key, value);
            Boolean result = Lazy.redisTemplate().execute((RedisConnection conn) ->
                    conn.scriptingCommands().eval(
                            UNLOCK_LUA_SCRIPT.getBytes(StandardCharsets.US_ASCII),
                            ReturnType.BOOLEAN, 1,
                            key.getBytes(StandardCharsets.UTF_8),
                            value.getBytes(StandardCharsets.UTF_8))
            );
            logger.info("tryUnlock(...) => result -> {}", result);
            return Boolean.TRUE.equals(result);
        }

    }

    /**
     * 懒加载实现单例模式.
     *
     * @see RedisUtils#setRedisTemplate(StringRedisTemplate)
     */
    private static class Lazy {

        /**
         * @see #redisTemplate()
         */
        private static StringRedisTemplate redisTemplate;

        private Lazy() {
            throw new IllegalAccessError("No instance");
        }

        /**
         * 获取{@link StringRedisTemplate}
         *
         * @see RedisUtils#setRedisTemplate(StringRedisTemplate)
         */
        private static StringRedisTemplate redisTemplate() {
            Assert.state(redisTemplate != null, () -> "'StringRedisTemplate' has not been initialized");
            return redisTemplate;
        }

    }

}
